1fc46f261SDan Klein# -*- coding: utf-8 -*-
2fc46f261SDan Klein#
3fc46f261SDan Klein# test/test_daemon.py
4fc46f261SDan Klein# Part of ‘python-daemon’, an implementation of PEP 3143.
5fc46f261SDan Klein#
6fc46f261SDan Klein# Copyright © 2008–2015 Ben Finney <ben+python@benfinney.id.au>
7fc46f261SDan Klein#
8fc46f261SDan Klein# This is free software: you may copy, modify, and/or distribute this work
9fc46f261SDan Klein# under the terms of the Apache License, version 2.0 as published by the
10fc46f261SDan Klein# Apache Software Foundation.
11fc46f261SDan Klein# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
12fc46f261SDan Klein
13fc46f261SDan Klein""" Unit test for ‘daemon’ module.
14fc46f261SDan Klein    """
15fc46f261SDan Klein
16fc46f261SDan Kleinfrom __future__ import (absolute_import, unicode_literals)
17fc46f261SDan Klein
18fc46f261SDan Kleinimport os
19fc46f261SDan Kleinimport sys
20fc46f261SDan Kleinimport tempfile
21fc46f261SDan Kleinimport resource
22fc46f261SDan Kleinimport errno
23fc46f261SDan Kleinimport signal
24fc46f261SDan Kleinimport socket
25fc46f261SDan Kleinfrom types import ModuleType
26fc46f261SDan Kleinimport collections
27fc46f261SDan Kleinimport functools
28fc46f261SDan Kleintry:
29fc46f261SDan Klein    # Standard library of Python 2.7 and later.
30fc46f261SDan Klein    from io import StringIO
31fc46f261SDan Kleinexcept ImportError:
32fc46f261SDan Klein    # Standard library of Python 2.6 and earlier.
33fc46f261SDan Klein    from StringIO import StringIO
34fc46f261SDan Klein
35fc46f261SDan Kleinimport mock
36fc46f261SDan Klein
37fc46f261SDan Kleinfrom . import scaffold
38fc46f261SDan Kleinfrom .scaffold import (basestring, unicode)
39fc46f261SDan Kleinfrom .test_pidfile import (
40fc46f261SDan Klein        FakeFileDescriptorStringIO,
41fc46f261SDan Klein        setup_pidfile_fixtures,
42fc46f261SDan Klein        )
43fc46f261SDan Klein
44fc46f261SDan Kleinimport daemon
45fc46f261SDan Klein
46fc46f261SDan Klein
47fc46f261SDan Kleinclass ModuleExceptions_TestCase(scaffold.Exception_TestCase):
48fc46f261SDan Klein    """ Test cases for module exception classes. """
49fc46f261SDan Klein
50fc46f261SDan Klein    scenarios = scaffold.make_exception_scenarios([
51fc46f261SDan Klein            ('daemon.daemon.DaemonError', dict(
52fc46f261SDan Klein                exc_type = daemon.daemon.DaemonError,
53fc46f261SDan Klein                min_args = 1,
54fc46f261SDan Klein                types = [Exception],
55fc46f261SDan Klein                )),
56fc46f261SDan Klein            ('daemon.daemon.DaemonOSEnvironmentError', dict(
57fc46f261SDan Klein                exc_type = daemon.daemon.DaemonOSEnvironmentError,
58fc46f261SDan Klein                min_args = 1,
59fc46f261SDan Klein                types = [daemon.daemon.DaemonError, OSError],
60fc46f261SDan Klein                )),
61fc46f261SDan Klein            ('daemon.daemon.DaemonProcessDetachError', dict(
62fc46f261SDan Klein                exc_type = daemon.daemon.DaemonProcessDetachError,
63fc46f261SDan Klein                min_args = 1,
64fc46f261SDan Klein                types = [daemon.daemon.DaemonError, OSError],
65fc46f261SDan Klein                )),
66fc46f261SDan Klein            ])
67fc46f261SDan Klein
68fc46f261SDan Klein
69fc46f261SDan Kleindef setup_daemon_context_fixtures(testcase):
70fc46f261SDan Klein    """ Set up common test fixtures for DaemonContext test case.
71fc46f261SDan Klein
72fc46f261SDan Klein        :param testcase: A ``TestCase`` instance to decorate.
73fc46f261SDan Klein        :return: ``None``.
74fc46f261SDan Klein
75fc46f261SDan Klein        Decorate the `testcase` with fixtures for tests involving
76fc46f261SDan Klein        `DaemonContext`.
77fc46f261SDan Klein
78fc46f261SDan Klein        """
79fc46f261SDan Klein    setup_streams_fixtures(testcase)
80fc46f261SDan Klein
81fc46f261SDan Klein    setup_pidfile_fixtures(testcase)
82fc46f261SDan Klein
83fc46f261SDan Klein    testcase.fake_pidfile_path = tempfile.mktemp()
84fc46f261SDan Klein    testcase.mock_pidlockfile = mock.MagicMock()
85fc46f261SDan Klein    testcase.mock_pidlockfile.path = testcase.fake_pidfile_path
86fc46f261SDan Klein
87fc46f261SDan Klein    testcase.daemon_context_args = dict(
88fc46f261SDan Klein            stdin=testcase.stream_files_by_name['stdin'],
89fc46f261SDan Klein            stdout=testcase.stream_files_by_name['stdout'],
90fc46f261SDan Klein            stderr=testcase.stream_files_by_name['stderr'],
91fc46f261SDan Klein            )
92fc46f261SDan Klein    testcase.test_instance = daemon.DaemonContext(
93fc46f261SDan Klein            **testcase.daemon_context_args)
94fc46f261SDan Klein
95fc46f261SDan Kleinfake_default_signal_map = object()
96fc46f261SDan Klein
97fc46f261SDan Klein@mock.patch.object(
98fc46f261SDan Klein        daemon.daemon, "is_detach_process_context_required",
99fc46f261SDan Klein        new=(lambda: True))
100fc46f261SDan Klein@mock.patch.object(
101fc46f261SDan Klein        daemon.daemon, "make_default_signal_map",
102fc46f261SDan Klein        new=(lambda: fake_default_signal_map))
103fc46f261SDan Klein@mock.patch.object(os, "setgid", new=(lambda x: object()))
104fc46f261SDan Klein@mock.patch.object(os, "setuid", new=(lambda x: object()))
105fc46f261SDan Kleinclass DaemonContext_BaseTestCase(scaffold.TestCase):
106fc46f261SDan Klein    """ Base class for DaemonContext test case classes. """
107fc46f261SDan Klein
108fc46f261SDan Klein    def setUp(self):
109fc46f261SDan Klein        """ Set up test fixtures. """
110fc46f261SDan Klein        super(DaemonContext_BaseTestCase, self).setUp()
111fc46f261SDan Klein
112fc46f261SDan Klein        setup_daemon_context_fixtures(self)
113fc46f261SDan Klein
114fc46f261SDan Klein
115fc46f261SDan Kleinclass DaemonContext_TestCase(DaemonContext_BaseTestCase):
116fc46f261SDan Klein    """ Test cases for DaemonContext class. """
117fc46f261SDan Klein
118fc46f261SDan Klein    def test_instantiate(self):
119fc46f261SDan Klein        """ New instance of DaemonContext should be created. """
120fc46f261SDan Klein        self.assertIsInstance(
121fc46f261SDan Klein                self.test_instance, daemon.daemon.DaemonContext)
122fc46f261SDan Klein
123fc46f261SDan Klein    def test_minimum_zero_arguments(self):
124fc46f261SDan Klein        """ Initialiser should not require any arguments. """
125fc46f261SDan Klein        instance = daemon.daemon.DaemonContext()
126fc46f261SDan Klein        self.assertIsNot(instance, None)
127fc46f261SDan Klein
128fc46f261SDan Klein    def test_has_specified_chroot_directory(self):
129fc46f261SDan Klein        """ Should have specified chroot_directory option. """
130fc46f261SDan Klein        args = dict(
131fc46f261SDan Klein                chroot_directory=object(),
132fc46f261SDan Klein                )
133fc46f261SDan Klein        expected_directory = args['chroot_directory']
134fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
135fc46f261SDan Klein        self.assertEqual(expected_directory, instance.chroot_directory)
136fc46f261SDan Klein
137fc46f261SDan Klein    def test_has_specified_working_directory(self):
138fc46f261SDan Klein        """ Should have specified working_directory option. """
139fc46f261SDan Klein        args = dict(
140fc46f261SDan Klein                working_directory=object(),
141fc46f261SDan Klein                )
142fc46f261SDan Klein        expected_directory = args['working_directory']
143fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
144fc46f261SDan Klein        self.assertEqual(expected_directory, instance.working_directory)
145fc46f261SDan Klein
146fc46f261SDan Klein    def test_has_default_working_directory(self):
147fc46f261SDan Klein        """ Should have default working_directory option. """
148fc46f261SDan Klein        args = dict()
149fc46f261SDan Klein        expected_directory = "/"
150fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
151fc46f261SDan Klein        self.assertEqual(expected_directory, instance.working_directory)
152fc46f261SDan Klein
153fc46f261SDan Klein    def test_has_specified_creation_mask(self):
154fc46f261SDan Klein        """ Should have specified umask option. """
155fc46f261SDan Klein        args = dict(
156fc46f261SDan Klein                umask=object(),
157fc46f261SDan Klein                )
158fc46f261SDan Klein        expected_mask = args['umask']
159fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
160fc46f261SDan Klein        self.assertEqual(expected_mask, instance.umask)
161fc46f261SDan Klein
162fc46f261SDan Klein    def test_has_default_creation_mask(self):
163fc46f261SDan Klein        """ Should have default umask option. """
164fc46f261SDan Klein        args = dict()
165fc46f261SDan Klein        expected_mask = 0
166fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
167fc46f261SDan Klein        self.assertEqual(expected_mask, instance.umask)
168fc46f261SDan Klein
169fc46f261SDan Klein    def test_has_specified_uid(self):
170fc46f261SDan Klein        """ Should have specified uid option. """
171fc46f261SDan Klein        args = dict(
172fc46f261SDan Klein                uid=object(),
173fc46f261SDan Klein                )
174fc46f261SDan Klein        expected_id = args['uid']
175fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
176fc46f261SDan Klein        self.assertEqual(expected_id, instance.uid)
177fc46f261SDan Klein
178fc46f261SDan Klein    def test_has_derived_uid(self):
179fc46f261SDan Klein        """ Should have uid option derived from process. """
180fc46f261SDan Klein        args = dict()
181fc46f261SDan Klein        expected_id = os.getuid()
182fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
183fc46f261SDan Klein        self.assertEqual(expected_id, instance.uid)
184fc46f261SDan Klein
185fc46f261SDan Klein    def test_has_specified_gid(self):
186fc46f261SDan Klein        """ Should have specified gid option. """
187fc46f261SDan Klein        args = dict(
188fc46f261SDan Klein                gid=object(),
189fc46f261SDan Klein                )
190fc46f261SDan Klein        expected_id = args['gid']
191fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
192fc46f261SDan Klein        self.assertEqual(expected_id, instance.gid)
193fc46f261SDan Klein
194fc46f261SDan Klein    def test_has_derived_gid(self):
195fc46f261SDan Klein        """ Should have gid option derived from process. """
196fc46f261SDan Klein        args = dict()
197fc46f261SDan Klein        expected_id = os.getgid()
198fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
199fc46f261SDan Klein        self.assertEqual(expected_id, instance.gid)
200fc46f261SDan Klein
201fc46f261SDan Klein    def test_has_specified_detach_process(self):
202fc46f261SDan Klein        """ Should have specified detach_process option. """
203fc46f261SDan Klein        args = dict(
204fc46f261SDan Klein                detach_process=object(),
205fc46f261SDan Klein                )
206fc46f261SDan Klein        expected_value = args['detach_process']
207fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
208fc46f261SDan Klein        self.assertEqual(expected_value, instance.detach_process)
209fc46f261SDan Klein
210fc46f261SDan Klein    def test_has_derived_detach_process(self):
211fc46f261SDan Klein        """ Should have detach_process option derived from environment. """
212fc46f261SDan Klein        args = dict()
213fc46f261SDan Klein        func = daemon.daemon.is_detach_process_context_required
214fc46f261SDan Klein        expected_value = func()
215fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
216fc46f261SDan Klein        self.assertEqual(expected_value, instance.detach_process)
217fc46f261SDan Klein
218fc46f261SDan Klein    def test_has_specified_files_preserve(self):
219fc46f261SDan Klein        """ Should have specified files_preserve option. """
220fc46f261SDan Klein        args = dict(
221fc46f261SDan Klein                files_preserve=object(),
222fc46f261SDan Klein                )
223fc46f261SDan Klein        expected_files_preserve = args['files_preserve']
224fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
225fc46f261SDan Klein        self.assertEqual(expected_files_preserve, instance.files_preserve)
226fc46f261SDan Klein
227fc46f261SDan Klein    def test_has_specified_pidfile(self):
228fc46f261SDan Klein        """ Should have the specified pidfile. """
229fc46f261SDan Klein        args = dict(
230fc46f261SDan Klein                pidfile=object(),
231fc46f261SDan Klein                )
232fc46f261SDan Klein        expected_pidfile = args['pidfile']
233fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
234fc46f261SDan Klein        self.assertEqual(expected_pidfile, instance.pidfile)
235fc46f261SDan Klein
236fc46f261SDan Klein    def test_has_specified_stdin(self):
237fc46f261SDan Klein        """ Should have specified stdin option. """
238fc46f261SDan Klein        args = dict(
239fc46f261SDan Klein                stdin=object(),
240fc46f261SDan Klein                )
241fc46f261SDan Klein        expected_file = args['stdin']
242fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
243fc46f261SDan Klein        self.assertEqual(expected_file, instance.stdin)
244fc46f261SDan Klein
245fc46f261SDan Klein    def test_has_specified_stdout(self):
246fc46f261SDan Klein        """ Should have specified stdout option. """
247fc46f261SDan Klein        args = dict(
248fc46f261SDan Klein                stdout=object(),
249fc46f261SDan Klein                )
250fc46f261SDan Klein        expected_file = args['stdout']
251fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
252fc46f261SDan Klein        self.assertEqual(expected_file, instance.stdout)
253fc46f261SDan Klein
254fc46f261SDan Klein    def test_has_specified_stderr(self):
255fc46f261SDan Klein        """ Should have specified stderr option. """
256fc46f261SDan Klein        args = dict(
257fc46f261SDan Klein                stderr=object(),
258fc46f261SDan Klein                )
259fc46f261SDan Klein        expected_file = args['stderr']
260fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
261fc46f261SDan Klein        self.assertEqual(expected_file, instance.stderr)
262fc46f261SDan Klein
263fc46f261SDan Klein    def test_has_specified_signal_map(self):
264fc46f261SDan Klein        """ Should have specified signal_map option. """
265fc46f261SDan Klein        args = dict(
266fc46f261SDan Klein                signal_map=object(),
267fc46f261SDan Klein                )
268fc46f261SDan Klein        expected_signal_map = args['signal_map']
269fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
270fc46f261SDan Klein        self.assertEqual(expected_signal_map, instance.signal_map)
271fc46f261SDan Klein
272fc46f261SDan Klein    def test_has_derived_signal_map(self):
273fc46f261SDan Klein        """ Should have signal_map option derived from system. """
274fc46f261SDan Klein        args = dict()
275fc46f261SDan Klein        expected_signal_map = daemon.daemon.make_default_signal_map()
276fc46f261SDan Klein        instance = daemon.daemon.DaemonContext(**args)
277fc46f261SDan Klein        self.assertEqual(expected_signal_map, instance.signal_map)
278fc46f261SDan Klein
279fc46f261SDan Klein
280fc46f261SDan Kleinclass DaemonContext_is_open_TestCase(DaemonContext_BaseTestCase):
281fc46f261SDan Klein    """ Test cases for DaemonContext.is_open property. """
282fc46f261SDan Klein
283fc46f261SDan Klein    def test_begin_false(self):
284fc46f261SDan Klein        """ Initial value of is_open should be False. """
285fc46f261SDan Klein        instance = self.test_instance
286fc46f261SDan Klein        self.assertEqual(False, instance.is_open)
287fc46f261SDan Klein
288fc46f261SDan Klein    def test_write_fails(self):
289fc46f261SDan Klein        """ Writing to is_open should fail. """
290fc46f261SDan Klein        instance = self.test_instance
291fc46f261SDan Klein        self.assertRaises(
292fc46f261SDan Klein                AttributeError,
293fc46f261SDan Klein                setattr, instance, 'is_open', object())
294fc46f261SDan Klein
295fc46f261SDan Klein
296fc46f261SDan Kleinclass DaemonContext_open_TestCase(DaemonContext_BaseTestCase):
297fc46f261SDan Klein    """ Test cases for DaemonContext.open method. """
298fc46f261SDan Klein
299fc46f261SDan Klein    def setUp(self):
300fc46f261SDan Klein        """ Set up test fixtures. """
301fc46f261SDan Klein        super(DaemonContext_open_TestCase, self).setUp()
302fc46f261SDan Klein
303fc46f261SDan Klein        self.test_instance._is_open = False
304fc46f261SDan Klein
305fc46f261SDan Klein        self.mock_module_daemon = mock.MagicMock()
306fc46f261SDan Klein        daemon_func_patchers = dict(
307fc46f261SDan Klein                (func_name, mock.patch.object(
308fc46f261SDan Klein                    daemon.daemon, func_name))
309fc46f261SDan Klein                for func_name in [
310fc46f261SDan Klein                    "detach_process_context",
311fc46f261SDan Klein                    "change_working_directory",
312fc46f261SDan Klein                    "change_root_directory",
313fc46f261SDan Klein                    "change_file_creation_mask",
314fc46f261SDan Klein                    "change_process_owner",
315fc46f261SDan Klein                    "prevent_core_dump",
316fc46f261SDan Klein                    "close_all_open_files",
317fc46f261SDan Klein                    "redirect_stream",
318fc46f261SDan Klein                    "set_signal_handlers",
319fc46f261SDan Klein                    "register_atexit_function",
320fc46f261SDan Klein                    ])
321fc46f261SDan Klein        for (func_name, patcher) in daemon_func_patchers.items():
322fc46f261SDan Klein            mock_func = patcher.start()
323fc46f261SDan Klein            self.addCleanup(patcher.stop)
324fc46f261SDan Klein            self.mock_module_daemon.attach_mock(mock_func, func_name)
325fc46f261SDan Klein
326fc46f261SDan Klein        self.mock_module_daemon.attach_mock(mock.Mock(), 'DaemonContext')
327fc46f261SDan Klein
328fc46f261SDan Klein        self.test_files_preserve_fds = object()
329fc46f261SDan Klein        self.test_signal_handler_map = object()
330fc46f261SDan Klein        daemoncontext_method_return_values = {
331fc46f261SDan Klein                '_get_exclude_file_descriptors':
332fc46f261SDan Klein                    self.test_files_preserve_fds,
333fc46f261SDan Klein                '_make_signal_handler_map':
334fc46f261SDan Klein                    self.test_signal_handler_map,
335fc46f261SDan Klein                }
336fc46f261SDan Klein        daemoncontext_func_patchers = dict(
337fc46f261SDan Klein                (func_name, mock.patch.object(
338fc46f261SDan Klein                    daemon.daemon.DaemonContext,
339fc46f261SDan Klein                    func_name,
340fc46f261SDan Klein                    return_value=return_value))
341fc46f261SDan Klein                for (func_name, return_value) in
342fc46f261SDan Klein                    daemoncontext_method_return_values.items())
343fc46f261SDan Klein        for (func_name, patcher) in daemoncontext_func_patchers.items():
344fc46f261SDan Klein            mock_func = patcher.start()
345fc46f261SDan Klein            self.addCleanup(patcher.stop)
346fc46f261SDan Klein            self.mock_module_daemon.DaemonContext.attach_mock(
347fc46f261SDan Klein                    mock_func, func_name)
348fc46f261SDan Klein
349fc46f261SDan Klein    def test_performs_steps_in_expected_sequence(self):
350fc46f261SDan Klein        """ Should perform daemonisation steps in expected sequence. """
351fc46f261SDan Klein        instance = self.test_instance
352fc46f261SDan Klein        instance.chroot_directory = object()
353fc46f261SDan Klein        instance.detach_process = True
354fc46f261SDan Klein        instance.pidfile = self.mock_pidlockfile
355fc46f261SDan Klein        self.mock_module_daemon.attach_mock(
356fc46f261SDan Klein                self.mock_pidlockfile, 'pidlockfile')
357fc46f261SDan Klein        expected_calls = [
358fc46f261SDan Klein                mock.call.change_root_directory(mock.ANY),
359fc46f261SDan Klein                mock.call.prevent_core_dump(),
360fc46f261SDan Klein                mock.call.change_file_creation_mask(mock.ANY),
361fc46f261SDan Klein                mock.call.change_working_directory(mock.ANY),
362fc46f261SDan Klein                mock.call.change_process_owner(mock.ANY, mock.ANY),
363fc46f261SDan Klein                mock.call.detach_process_context(),
364fc46f261SDan Klein                mock.call.DaemonContext._make_signal_handler_map(),
365fc46f261SDan Klein                mock.call.set_signal_handlers(mock.ANY),
366fc46f261SDan Klein                mock.call.DaemonContext._get_exclude_file_descriptors(),
367fc46f261SDan Klein                mock.call.close_all_open_files(exclude=mock.ANY),
368fc46f261SDan Klein                mock.call.redirect_stream(mock.ANY, mock.ANY),
369fc46f261SDan Klein                mock.call.redirect_stream(mock.ANY, mock.ANY),
370fc46f261SDan Klein                mock.call.redirect_stream(mock.ANY, mock.ANY),
371fc46f261SDan Klein                mock.call.pidlockfile.__enter__(),
372fc46f261SDan Klein                mock.call.register_atexit_function(mock.ANY),
373fc46f261SDan Klein                ]
374fc46f261SDan Klein        instance.open()
375fc46f261SDan Klein        self.mock_module_daemon.assert_has_calls(expected_calls)
376fc46f261SDan Klein
377fc46f261SDan Klein    def test_returns_immediately_if_is_open(self):
378fc46f261SDan Klein        """ Should return immediately if is_open property is true. """
379fc46f261SDan Klein        instance = self.test_instance
380fc46f261SDan Klein        instance._is_open = True
381fc46f261SDan Klein        instance.open()
382fc46f261SDan Klein        self.assertEqual(0, len(self.mock_module_daemon.mock_calls))
383fc46f261SDan Klein
384fc46f261SDan Klein    def test_changes_root_directory_to_chroot_directory(self):
385fc46f261SDan Klein        """ Should change root directory to `chroot_directory` option. """
386fc46f261SDan Klein        instance = self.test_instance
387fc46f261SDan Klein        chroot_directory = object()
388fc46f261SDan Klein        instance.chroot_directory = chroot_directory
389fc46f261SDan Klein        instance.open()
390fc46f261SDan Klein        self.mock_module_daemon.change_root_directory.assert_called_with(
391fc46f261SDan Klein                chroot_directory)
392fc46f261SDan Klein
393fc46f261SDan Klein    def test_omits_chroot_if_no_chroot_directory(self):
394fc46f261SDan Klein        """ Should omit changing root directory if no `chroot_directory`. """
395fc46f261SDan Klein        instance = self.test_instance
396fc46f261SDan Klein        instance.chroot_directory = None
397fc46f261SDan Klein        instance.open()
398fc46f261SDan Klein        self.assertFalse(self.mock_module_daemon.change_root_directory.called)
399fc46f261SDan Klein
400fc46f261SDan Klein    def test_prevents_core_dump(self):
401fc46f261SDan Klein        """ Should request prevention of core dumps. """
402fc46f261SDan Klein        instance = self.test_instance
403fc46f261SDan Klein        instance.open()
404fc46f261SDan Klein        self.mock_module_daemon.prevent_core_dump.assert_called_with()
405fc46f261SDan Klein
406fc46f261SDan Klein    def test_omits_prevent_core_dump_if_prevent_core_false(self):
407fc46f261SDan Klein        """ Should omit preventing core dumps if `prevent_core` is false. """
408fc46f261SDan Klein        instance = self.test_instance
409fc46f261SDan Klein        instance.prevent_core = False
410fc46f261SDan Klein        instance.open()
411fc46f261SDan Klein        self.assertFalse(self.mock_module_daemon.prevent_core_dump.called)
412fc46f261SDan Klein
413fc46f261SDan Klein    def test_closes_open_files(self):
414fc46f261SDan Klein        """ Should close all open files, excluding `files_preserve`. """
415fc46f261SDan Klein        instance = self.test_instance
416fc46f261SDan Klein        expected_exclude = self.test_files_preserve_fds
417fc46f261SDan Klein        instance.open()
418fc46f261SDan Klein        self.mock_module_daemon.close_all_open_files.assert_called_with(
419fc46f261SDan Klein                exclude=expected_exclude)
420fc46f261SDan Klein
421fc46f261SDan Klein    def test_changes_directory_to_working_directory(self):
422fc46f261SDan Klein        """ Should change current directory to `working_directory` option. """
423fc46f261SDan Klein        instance = self.test_instance
424fc46f261SDan Klein        working_directory = object()
425fc46f261SDan Klein        instance.working_directory = working_directory
426fc46f261SDan Klein        instance.open()
427fc46f261SDan Klein        self.mock_module_daemon.change_working_directory.assert_called_with(
428fc46f261SDan Klein                working_directory)
429fc46f261SDan Klein
430fc46f261SDan Klein    def test_changes_creation_mask_to_umask(self):
431fc46f261SDan Klein        """ Should change file creation mask to `umask` option. """
432fc46f261SDan Klein        instance = self.test_instance
433fc46f261SDan Klein        umask = object()
434fc46f261SDan Klein        instance.umask = umask
435fc46f261SDan Klein        instance.open()
436fc46f261SDan Klein        self.mock_module_daemon.change_file_creation_mask.assert_called_with(
437fc46f261SDan Klein                umask)
438fc46f261SDan Klein
439fc46f261SDan Klein    def test_changes_owner_to_specified_uid_and_gid(self):
440fc46f261SDan Klein        """ Should change process UID and GID to `uid` and `gid` options. """
441fc46f261SDan Klein        instance = self.test_instance
442fc46f261SDan Klein        uid = object()
443fc46f261SDan Klein        gid = object()
444fc46f261SDan Klein        instance.uid = uid
445fc46f261SDan Klein        instance.gid = gid
446fc46f261SDan Klein        instance.open()
447fc46f261SDan Klein        self.mock_module_daemon.change_process_owner.assert_called_with(
448fc46f261SDan Klein                uid, gid)
449fc46f261SDan Klein
450fc46f261SDan Klein    def test_detaches_process_context(self):
451fc46f261SDan Klein        """ Should request detach of process context. """
452fc46f261SDan Klein        instance = self.test_instance
453fc46f261SDan Klein        instance.open()
454fc46f261SDan Klein        self.mock_module_daemon.detach_process_context.assert_called_with()
455fc46f261SDan Klein
456fc46f261SDan Klein    def test_omits_process_detach_if_not_required(self):
457fc46f261SDan Klein        """ Should omit detach of process context if not required. """
458fc46f261SDan Klein        instance = self.test_instance
459fc46f261SDan Klein        instance.detach_process = False
460fc46f261SDan Klein        instance.open()
461fc46f261SDan Klein        self.assertFalse(self.mock_module_daemon.detach_process_context.called)
462fc46f261SDan Klein
463fc46f261SDan Klein    def test_sets_signal_handlers_from_signal_map(self):
464fc46f261SDan Klein        """ Should set signal handlers according to `signal_map`. """
465fc46f261SDan Klein        instance = self.test_instance
466fc46f261SDan Klein        instance.signal_map = object()
467fc46f261SDan Klein        expected_signal_handler_map = self.test_signal_handler_map
468fc46f261SDan Klein        instance.open()
469fc46f261SDan Klein        self.mock_module_daemon.set_signal_handlers.assert_called_with(
470fc46f261SDan Klein                expected_signal_handler_map)
471fc46f261SDan Klein
472fc46f261SDan Klein    def test_redirects_standard_streams(self):
473fc46f261SDan Klein        """ Should request redirection of standard stream files. """
474fc46f261SDan Klein        instance = self.test_instance
475fc46f261SDan Klein        (system_stdin, system_stdout, system_stderr) = (
476fc46f261SDan Klein                sys.stdin, sys.stdout, sys.stderr)
477fc46f261SDan Klein        (target_stdin, target_stdout, target_stderr) = (
478fc46f261SDan Klein                self.stream_files_by_name[name]
479fc46f261SDan Klein                for name in ['stdin', 'stdout', 'stderr'])
480fc46f261SDan Klein        expected_calls = [
481fc46f261SDan Klein                mock.call(system_stdin, target_stdin),
482fc46f261SDan Klein                mock.call(system_stdout, target_stdout),
483fc46f261SDan Klein                mock.call(system_stderr, target_stderr),
484fc46f261SDan Klein                ]
485fc46f261SDan Klein        instance.open()
486fc46f261SDan Klein        self.mock_module_daemon.redirect_stream.assert_has_calls(
487fc46f261SDan Klein                expected_calls, any_order=True)
488fc46f261SDan Klein
489fc46f261SDan Klein    def test_enters_pidfile_context(self):
490fc46f261SDan Klein        """ Should enter the PID file context manager. """
491fc46f261SDan Klein        instance = self.test_instance
492fc46f261SDan Klein        instance.pidfile = self.mock_pidlockfile
493fc46f261SDan Klein        instance.open()
494fc46f261SDan Klein        self.mock_pidlockfile.__enter__.assert_called_with()
495fc46f261SDan Klein
496fc46f261SDan Klein    def test_sets_is_open_true(self):
497fc46f261SDan Klein        """ Should set the `is_open` property to True. """
498fc46f261SDan Klein        instance = self.test_instance
499fc46f261SDan Klein        instance.open()
500fc46f261SDan Klein        self.assertEqual(True, instance.is_open)
501fc46f261SDan Klein
502fc46f261SDan Klein    def test_registers_close_method_for_atexit(self):
503fc46f261SDan Klein        """ Should register the `close` method for atexit processing. """
504fc46f261SDan Klein        instance = self.test_instance
505fc46f261SDan Klein        close_method = instance.close
506fc46f261SDan Klein        instance.open()
507fc46f261SDan Klein        self.mock_module_daemon.register_atexit_function.assert_called_with(
508fc46f261SDan Klein                close_method)
509fc46f261SDan Klein
510fc46f261SDan Klein
511fc46f261SDan Kleinclass DaemonContext_close_TestCase(DaemonContext_BaseTestCase):
512fc46f261SDan Klein    """ Test cases for DaemonContext.close method. """
513fc46f261SDan Klein
514fc46f261SDan Klein    def setUp(self):
515fc46f261SDan Klein        """ Set up test fixtures. """
516fc46f261SDan Klein        super(DaemonContext_close_TestCase, self).setUp()
517fc46f261SDan Klein
518fc46f261SDan Klein        self.test_instance._is_open = True
519fc46f261SDan Klein
520fc46f261SDan Klein    def test_returns_immediately_if_not_is_open(self):
521fc46f261SDan Klein        """ Should return immediately if is_open property is false. """
522fc46f261SDan Klein        instance = self.test_instance
523fc46f261SDan Klein        instance._is_open = False
524fc46f261SDan Klein        instance.pidfile = object()
525fc46f261SDan Klein        instance.close()
526fc46f261SDan Klein        self.assertFalse(self.mock_pidlockfile.__exit__.called)
527fc46f261SDan Klein
528fc46f261SDan Klein    def test_exits_pidfile_context(self):
529fc46f261SDan Klein        """ Should exit the PID file context manager. """
530fc46f261SDan Klein        instance = self.test_instance
531fc46f261SDan Klein        instance.pidfile = self.mock_pidlockfile
532fc46f261SDan Klein        instance.close()
533fc46f261SDan Klein        self.mock_pidlockfile.__exit__.assert_called_with(None, None, None)
534fc46f261SDan Klein
535fc46f261SDan Klein    def test_returns_none(self):
536fc46f261SDan Klein        """ Should return None. """
537fc46f261SDan Klein        instance = self.test_instance
538fc46f261SDan Klein        expected_result = None
539fc46f261SDan Klein        result = instance.close()
540fc46f261SDan Klein        self.assertIs(result, expected_result)
541fc46f261SDan Klein
542fc46f261SDan Klein    def test_sets_is_open_false(self):
543fc46f261SDan Klein        """ Should set the `is_open` property to False. """
544fc46f261SDan Klein        instance = self.test_instance
545fc46f261SDan Klein        instance.close()
546fc46f261SDan Klein        self.assertEqual(False, instance.is_open)
547fc46f261SDan Klein
548fc46f261SDan Klein
549fc46f261SDan Klein@mock.patch.object(daemon.daemon.DaemonContext, "open")
550fc46f261SDan Kleinclass DaemonContext_context_manager_enter_TestCase(DaemonContext_BaseTestCase):
551fc46f261SDan Klein    """ Test cases for DaemonContext.__enter__ method. """
552fc46f261SDan Klein
553fc46f261SDan Klein    def test_opens_daemon_context(self, mock_func_daemoncontext_open):
554fc46f261SDan Klein        """ Should open the DaemonContext. """
555fc46f261SDan Klein        instance = self.test_instance
556fc46f261SDan Klein        instance.__enter__()
557fc46f261SDan Klein        mock_func_daemoncontext_open.assert_called_with()
558fc46f261SDan Klein
559fc46f261SDan Klein    def test_returns_self_instance(self, mock_func_daemoncontext_open):
560fc46f261SDan Klein        """ Should return DaemonContext instance. """
561fc46f261SDan Klein        instance = self.test_instance
562fc46f261SDan Klein        expected_result = instance
563fc46f261SDan Klein        result = instance.__enter__()
564fc46f261SDan Klein        self.assertIs(result, expected_result)
565fc46f261SDan Klein
566fc46f261SDan Klein
567fc46f261SDan Klein@mock.patch.object(daemon.daemon.DaemonContext, "close")
568fc46f261SDan Kleinclass DaemonContext_context_manager_exit_TestCase(DaemonContext_BaseTestCase):
569fc46f261SDan Klein    """ Test cases for DaemonContext.__exit__ method. """
570fc46f261SDan Klein
571fc46f261SDan Klein    def setUp(self):
572fc46f261SDan Klein        """ Set up test fixtures. """
573fc46f261SDan Klein        super(DaemonContext_context_manager_exit_TestCase, self).setUp()
574fc46f261SDan Klein
575fc46f261SDan Klein        self.test_args = dict(
576fc46f261SDan Klein                exc_type=object(),
577fc46f261SDan Klein                exc_value=object(),
578fc46f261SDan Klein                traceback=object(),
579fc46f261SDan Klein                )
580fc46f261SDan Klein
581fc46f261SDan Klein    def test_closes_daemon_context(self, mock_func_daemoncontext_close):
582fc46f261SDan Klein        """ Should close the DaemonContext. """
583fc46f261SDan Klein        instance = self.test_instance
584fc46f261SDan Klein        args = self.test_args
585fc46f261SDan Klein        instance.__exit__(**args)
586fc46f261SDan Klein        mock_func_daemoncontext_close.assert_called_with()
587fc46f261SDan Klein
588fc46f261SDan Klein    def test_returns_none(self, mock_func_daemoncontext_close):
589fc46f261SDan Klein        """ Should return None, indicating exception was not handled. """
590fc46f261SDan Klein        instance = self.test_instance
591fc46f261SDan Klein        args = self.test_args
592fc46f261SDan Klein        expected_result = None
593fc46f261SDan Klein        result = instance.__exit__(**args)
594fc46f261SDan Klein        self.assertIs(result, expected_result)
595fc46f261SDan Klein
596fc46f261SDan Klein
597fc46f261SDan Kleinclass DaemonContext_terminate_TestCase(DaemonContext_BaseTestCase):
598fc46f261SDan Klein    """ Test cases for DaemonContext.terminate method. """
599fc46f261SDan Klein
600fc46f261SDan Klein    def setUp(self):
601fc46f261SDan Klein        """ Set up test fixtures. """
602fc46f261SDan Klein        super(DaemonContext_terminate_TestCase, self).setUp()
603fc46f261SDan Klein
604fc46f261SDan Klein        self.test_signal = signal.SIGTERM
605fc46f261SDan Klein        self.test_frame = None
606fc46f261SDan Klein        self.test_args = (self.test_signal, self.test_frame)
607fc46f261SDan Klein
608fc46f261SDan Klein    def test_raises_system_exit(self):
609fc46f261SDan Klein        """ Should raise SystemExit. """
610fc46f261SDan Klein        instance = self.test_instance
611fc46f261SDan Klein        args = self.test_args
612fc46f261SDan Klein        expected_exception = SystemExit
613fc46f261SDan Klein        self.assertRaises(
614fc46f261SDan Klein                expected_exception,
615fc46f261SDan Klein                instance.terminate, *args)
616fc46f261SDan Klein
617fc46f261SDan Klein    def test_exception_message_contains_signal_number(self):
618fc46f261SDan Klein        """ Should raise exception with a message containing signal number. """
619fc46f261SDan Klein        instance = self.test_instance
620fc46f261SDan Klein        args = self.test_args
621fc46f261SDan Klein        signal_number = self.test_signal
622fc46f261SDan Klein        expected_exception = SystemExit
623fc46f261SDan Klein        exc = self.assertRaises(
624fc46f261SDan Klein                expected_exception,
625fc46f261SDan Klein                instance.terminate, *args)
626fc46f261SDan Klein        self.assertIn(unicode(signal_number), unicode(exc))
627fc46f261SDan Klein
628fc46f261SDan Klein
629fc46f261SDan Kleinclass DaemonContext_get_exclude_file_descriptors_TestCase(
630fc46f261SDan Klein        DaemonContext_BaseTestCase):
631fc46f261SDan Klein    """ Test cases for DaemonContext._get_exclude_file_descriptors function. """
632fc46f261SDan Klein
633fc46f261SDan Klein    def setUp(self):
634fc46f261SDan Klein        """ Set up test fixtures. """
635fc46f261SDan Klein        super(
636fc46f261SDan Klein                DaemonContext_get_exclude_file_descriptors_TestCase,
637fc46f261SDan Klein                self).setUp()
638fc46f261SDan Klein
639fc46f261SDan Klein        self.test_files = {
640fc46f261SDan Klein                2: FakeFileDescriptorStringIO(),
641fc46f261SDan Klein                5: 5,
642fc46f261SDan Klein                11: FakeFileDescriptorStringIO(),
643fc46f261SDan Klein                17: None,
644fc46f261SDan Klein                23: FakeFileDescriptorStringIO(),
645fc46f261SDan Klein                37: 37,
646fc46f261SDan Klein                42: FakeFileDescriptorStringIO(),
647fc46f261SDan Klein                }
648fc46f261SDan Klein        for (fileno, item) in self.test_files.items():
649fc46f261SDan Klein            if hasattr(item, '_fileno'):
650fc46f261SDan Klein                item._fileno = fileno
651fc46f261SDan Klein        self.test_file_descriptors = set(
652fc46f261SDan Klein                fd for (fd, item) in self.test_files.items()
653fc46f261SDan Klein                if item is not None)
654fc46f261SDan Klein        self.test_file_descriptors.update(
655fc46f261SDan Klein                self.stream_files_by_name[name].fileno()
656fc46f261SDan Klein                for name in ['stdin', 'stdout', 'stderr']
657fc46f261SDan Klein                )
658fc46f261SDan Klein
659fc46f261SDan Klein    def test_returns_expected_file_descriptors(self):
660fc46f261SDan Klein        """ Should return expected set of file descriptors. """
661fc46f261SDan Klein        instance = self.test_instance
662fc46f261SDan Klein        instance.files_preserve = list(self.test_files.values())
663fc46f261SDan Klein        expected_result = self.test_file_descriptors
664fc46f261SDan Klein        result = instance._get_exclude_file_descriptors()
665fc46f261SDan Klein        self.assertEqual(expected_result, result)
666fc46f261SDan Klein
667fc46f261SDan Klein    def test_returns_stream_redirects_if_no_files_preserve(self):
668fc46f261SDan Klein        """ Should return only stream redirects if no files_preserve. """
669fc46f261SDan Klein        instance = self.test_instance
670fc46f261SDan Klein        instance.files_preserve = None
671fc46f261SDan Klein        expected_result = set(
672fc46f261SDan Klein                stream.fileno()
673fc46f261SDan Klein                for stream in self.stream_files_by_name.values())
674fc46f261SDan Klein        result = instance._get_exclude_file_descriptors()
675fc46f261SDan Klein        self.assertEqual(expected_result, result)
676fc46f261SDan Klein
677fc46f261SDan Klein    def test_returns_empty_set_if_no_files(self):
678fc46f261SDan Klein        """ Should return empty set if no file options. """
679fc46f261SDan Klein        instance = self.test_instance
680fc46f261SDan Klein        for name in ['files_preserve', 'stdin', 'stdout', 'stderr']:
681fc46f261SDan Klein            setattr(instance, name, None)
682fc46f261SDan Klein        expected_result = set()
683fc46f261SDan Klein        result = instance._get_exclude_file_descriptors()
684fc46f261SDan Klein        self.assertEqual(expected_result, result)
685fc46f261SDan Klein
686fc46f261SDan Klein    def test_omits_non_file_streams(self):
687fc46f261SDan Klein        """ Should omit non-file stream attributes. """
688fc46f261SDan Klein        instance = self.test_instance
689fc46f261SDan Klein        instance.files_preserve = list(self.test_files.values())
690fc46f261SDan Klein        stream_files = self.stream_files_by_name
691fc46f261SDan Klein        expected_result = self.test_file_descriptors.copy()
692fc46f261SDan Klein        for (pseudo_stream_name, pseudo_stream) in stream_files.items():
693fc46f261SDan Klein            test_non_file_object = object()
694fc46f261SDan Klein            setattr(instance, pseudo_stream_name, test_non_file_object)
695fc46f261SDan Klein            stream_fd = pseudo_stream.fileno()
696fc46f261SDan Klein            expected_result.discard(stream_fd)
697fc46f261SDan Klein        result = instance._get_exclude_file_descriptors()
698fc46f261SDan Klein        self.assertEqual(expected_result, result)
699fc46f261SDan Klein
700fc46f261SDan Klein    def test_includes_verbatim_streams_without_file_descriptor(self):
701fc46f261SDan Klein        """ Should include verbatim any stream without a file descriptor. """
702fc46f261SDan Klein        instance = self.test_instance
703fc46f261SDan Klein        instance.files_preserve = list(self.test_files.values())
704fc46f261SDan Klein        stream_files = self.stream_files_by_name
705fc46f261SDan Klein        mock_fileno_method = mock.MagicMock(
706fc46f261SDan Klein                spec=sys.__stdin__.fileno,
707fc46f261SDan Klein                side_effect=ValueError)
708fc46f261SDan Klein        expected_result = self.test_file_descriptors.copy()
709fc46f261SDan Klein        for (pseudo_stream_name, pseudo_stream) in stream_files.items():
710fc46f261SDan Klein            test_non_fd_stream = StringIO()
711fc46f261SDan Klein            if not hasattr(test_non_fd_stream, 'fileno'):
712fc46f261SDan Klein                # Python < 3 StringIO doesn't have ‘fileno’ at all.
713fc46f261SDan Klein                # Add a method which raises an exception.
714fc46f261SDan Klein                test_non_fd_stream.fileno = mock_fileno_method
715fc46f261SDan Klein            setattr(instance, pseudo_stream_name, test_non_fd_stream)
716fc46f261SDan Klein            stream_fd = pseudo_stream.fileno()
717fc46f261SDan Klein            expected_result.discard(stream_fd)
718fc46f261SDan Klein            expected_result.add(test_non_fd_stream)
719fc46f261SDan Klein        result = instance._get_exclude_file_descriptors()
720fc46f261SDan Klein        self.assertEqual(expected_result, result)
721fc46f261SDan Klein
722fc46f261SDan Klein    def test_omits_none_streams(self):
723fc46f261SDan Klein        """ Should omit any stream attribute which is None. """
724fc46f261SDan Klein        instance = self.test_instance
725fc46f261SDan Klein        instance.files_preserve = list(self.test_files.values())
726fc46f261SDan Klein        stream_files = self.stream_files_by_name
727fc46f261SDan Klein        expected_result = self.test_file_descriptors.copy()
728fc46f261SDan Klein        for (pseudo_stream_name, pseudo_stream) in stream_files.items():
729fc46f261SDan Klein            setattr(instance, pseudo_stream_name, None)
730fc46f261SDan Klein            stream_fd = pseudo_stream.fileno()
731fc46f261SDan Klein            expected_result.discard(stream_fd)
732fc46f261SDan Klein        result = instance._get_exclude_file_descriptors()
733fc46f261SDan Klein        self.assertEqual(expected_result, result)
734fc46f261SDan Klein
735fc46f261SDan Klein
736fc46f261SDan Kleinclass DaemonContext_make_signal_handler_TestCase(DaemonContext_BaseTestCase):
737fc46f261SDan Klein    """ Test cases for DaemonContext._make_signal_handler function. """
738fc46f261SDan Klein
739fc46f261SDan Klein    def test_returns_ignore_for_none(self):
740fc46f261SDan Klein        """ Should return SIG_IGN when None handler specified. """
741fc46f261SDan Klein        instance = self.test_instance
742fc46f261SDan Klein        target = None
743fc46f261SDan Klein        expected_result = signal.SIG_IGN
744fc46f261SDan Klein        result = instance._make_signal_handler(target)
745fc46f261SDan Klein        self.assertEqual(expected_result, result)
746fc46f261SDan Klein
747fc46f261SDan Klein    def test_returns_method_for_name(self):
748fc46f261SDan Klein        """ Should return method of DaemonContext when name specified. """
749fc46f261SDan Klein        instance = self.test_instance
750fc46f261SDan Klein        target = 'terminate'
751fc46f261SDan Klein        expected_result = instance.terminate
752fc46f261SDan Klein        result = instance._make_signal_handler(target)
753fc46f261SDan Klein        self.assertEqual(expected_result, result)
754fc46f261SDan Klein
755fc46f261SDan Klein    def test_raises_error_for_unknown_name(self):
756fc46f261SDan Klein        """ Should raise AttributeError for unknown method name. """
757fc46f261SDan Klein        instance = self.test_instance
758fc46f261SDan Klein        target = 'b0gUs'
759fc46f261SDan Klein        expected_error = AttributeError
760fc46f261SDan Klein        self.assertRaises(
761fc46f261SDan Klein                expected_error,
762fc46f261SDan Klein                instance._make_signal_handler, target)
763fc46f261SDan Klein
764fc46f261SDan Klein    def test_returns_object_for_object(self):
765fc46f261SDan Klein        """ Should return same object for any other object. """
766fc46f261SDan Klein        instance = self.test_instance
767fc46f261SDan Klein        target = object()
768fc46f261SDan Klein        expected_result = target
769fc46f261SDan Klein        result = instance._make_signal_handler(target)
770fc46f261SDan Klein        self.assertEqual(expected_result, result)
771fc46f261SDan Klein
772fc46f261SDan Klein
773fc46f261SDan Kleinclass DaemonContext_make_signal_handler_map_TestCase(
774fc46f261SDan Klein        DaemonContext_BaseTestCase):
775fc46f261SDan Klein    """ Test cases for DaemonContext._make_signal_handler_map function. """
776fc46f261SDan Klein
777fc46f261SDan Klein    def setUp(self):
778fc46f261SDan Klein        """ Set up test fixtures. """
779fc46f261SDan Klein        super(DaemonContext_make_signal_handler_map_TestCase, self).setUp()
780fc46f261SDan Klein
781fc46f261SDan Klein        self.test_instance.signal_map = {
782fc46f261SDan Klein                object(): object(),
783fc46f261SDan Klein                object(): object(),
784fc46f261SDan Klein                object(): object(),
785fc46f261SDan Klein                }
786fc46f261SDan Klein
787fc46f261SDan Klein        self.test_signal_handlers = dict(
788fc46f261SDan Klein                (key, object())
789fc46f261SDan Klein                for key in self.test_instance.signal_map.values())
790fc46f261SDan Klein        self.test_signal_handler_map = dict(
791fc46f261SDan Klein                (key, self.test_signal_handlers[target])
792fc46f261SDan Klein                for (key, target) in self.test_instance.signal_map.items())
793fc46f261SDan Klein
794fc46f261SDan Klein        def fake_make_signal_handler(target):
795fc46f261SDan Klein            return self.test_signal_handlers[target]
796fc46f261SDan Klein
797fc46f261SDan Klein        func_patcher_make_signal_handler = mock.patch.object(
798fc46f261SDan Klein                daemon.daemon.DaemonContext, "_make_signal_handler",
799fc46f261SDan Klein                side_effect=fake_make_signal_handler)
800fc46f261SDan Klein        self.mock_func_make_signal_handler = (
801fc46f261SDan Klein                func_patcher_make_signal_handler.start())
802fc46f261SDan Klein        self.addCleanup(func_patcher_make_signal_handler.stop)
803fc46f261SDan Klein
804fc46f261SDan Klein    def test_returns_constructed_signal_handler_items(self):
805fc46f261SDan Klein        """ Should return items as constructed via make_signal_handler. """
806fc46f261SDan Klein        instance = self.test_instance
807fc46f261SDan Klein        expected_result = self.test_signal_handler_map
808fc46f261SDan Klein        result = instance._make_signal_handler_map()
809fc46f261SDan Klein        self.assertEqual(expected_result, result)
810fc46f261SDan Klein
811fc46f261SDan Klein
812fc46f261SDan Kleintry:
813fc46f261SDan Klein    FileNotFoundError
814fc46f261SDan Kleinexcept NameError:
815fc46f261SDan Klein    # Python 2 uses IOError.
816fc46f261SDan Klein    FileNotFoundError = functools.partial(IOError, errno.ENOENT)
817fc46f261SDan Klein
818fc46f261SDan Klein
819fc46f261SDan Klein@mock.patch.object(os, "chdir")
820fc46f261SDan Kleinclass change_working_directory_TestCase(scaffold.TestCase):
821fc46f261SDan Klein    """ Test cases for change_working_directory function. """
822fc46f261SDan Klein
823fc46f261SDan Klein    def setUp(self):
824fc46f261SDan Klein        """ Set up test fixtures. """
825fc46f261SDan Klein        super(change_working_directory_TestCase, self).setUp()
826fc46f261SDan Klein
827fc46f261SDan Klein        self.test_directory = object()
828fc46f261SDan Klein        self.test_args = dict(
829fc46f261SDan Klein                directory=self.test_directory,
830fc46f261SDan Klein                )
831fc46f261SDan Klein
832fc46f261SDan Klein    def test_changes_working_directory_to_specified_directory(
833fc46f261SDan Klein            self,
834fc46f261SDan Klein            mock_func_os_chdir):
835fc46f261SDan Klein        """ Should change working directory to specified directory. """
836fc46f261SDan Klein        args = self.test_args
837fc46f261SDan Klein        directory = self.test_directory
838fc46f261SDan Klein        daemon.daemon.change_working_directory(**args)
839fc46f261SDan Klein        mock_func_os_chdir.assert_called_with(directory)
840fc46f261SDan Klein
841fc46f261SDan Klein    def test_raises_daemon_error_on_os_error(
842fc46f261SDan Klein            self,
843fc46f261SDan Klein            mock_func_os_chdir):
844fc46f261SDan Klein        """ Should raise a DaemonError on receiving an IOError. """
845fc46f261SDan Klein        args = self.test_args
846fc46f261SDan Klein        test_error = FileNotFoundError("No such directory")
847fc46f261SDan Klein        mock_func_os_chdir.side_effect = test_error
848fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
849fc46f261SDan Klein        exc = self.assertRaises(
850fc46f261SDan Klein                expected_error,
851fc46f261SDan Klein                daemon.daemon.change_working_directory, **args)
852fc46f261SDan Klein        self.assertEqual(test_error, exc.__cause__)
853fc46f261SDan Klein
854fc46f261SDan Klein    def test_error_message_contains_original_error_message(
855fc46f261SDan Klein            self,
856fc46f261SDan Klein            mock_func_os_chdir):
857fc46f261SDan Klein        """ Should raise a DaemonError with original message. """
858fc46f261SDan Klein        args = self.test_args
859fc46f261SDan Klein        test_error = FileNotFoundError("No such directory")
860fc46f261SDan Klein        mock_func_os_chdir.side_effect = test_error
861fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
862fc46f261SDan Klein        exc = self.assertRaises(
863fc46f261SDan Klein                expected_error,
864fc46f261SDan Klein                daemon.daemon.change_working_directory, **args)
865fc46f261SDan Klein        self.assertIn(unicode(test_error), unicode(exc))
866fc46f261SDan Klein
867fc46f261SDan Klein
868fc46f261SDan Klein@mock.patch.object(os, "chroot")
869fc46f261SDan Klein@mock.patch.object(os, "chdir")
870fc46f261SDan Kleinclass change_root_directory_TestCase(scaffold.TestCase):
871fc46f261SDan Klein    """ Test cases for change_root_directory function. """
872fc46f261SDan Klein
873fc46f261SDan Klein    def setUp(self):
874fc46f261SDan Klein        """ Set up test fixtures. """
875fc46f261SDan Klein        super(change_root_directory_TestCase, self).setUp()
876fc46f261SDan Klein
877fc46f261SDan Klein        self.test_directory = object()
878fc46f261SDan Klein        self.test_args = dict(
879fc46f261SDan Klein                directory=self.test_directory,
880fc46f261SDan Klein                )
881fc46f261SDan Klein
882fc46f261SDan Klein    def test_changes_working_directory_to_specified_directory(
883fc46f261SDan Klein            self,
884fc46f261SDan Klein            mock_func_os_chdir, mock_func_os_chroot):
885fc46f261SDan Klein        """ Should change working directory to specified directory. """
886fc46f261SDan Klein        args = self.test_args
887fc46f261SDan Klein        directory = self.test_directory
888fc46f261SDan Klein        daemon.daemon.change_root_directory(**args)
889fc46f261SDan Klein        mock_func_os_chdir.assert_called_with(directory)
890fc46f261SDan Klein
891fc46f261SDan Klein    def test_changes_root_directory_to_specified_directory(
892fc46f261SDan Klein            self,
893fc46f261SDan Klein            mock_func_os_chdir, mock_func_os_chroot):
894fc46f261SDan Klein        """ Should change root directory to specified directory. """
895fc46f261SDan Klein        args = self.test_args
896fc46f261SDan Klein        directory = self.test_directory
897fc46f261SDan Klein        daemon.daemon.change_root_directory(**args)
898fc46f261SDan Klein        mock_func_os_chroot.assert_called_with(directory)
899fc46f261SDan Klein
900fc46f261SDan Klein    def test_raises_daemon_error_on_os_error_from_chdir(
901fc46f261SDan Klein            self,
902fc46f261SDan Klein            mock_func_os_chdir, mock_func_os_chroot):
903fc46f261SDan Klein        """ Should raise a DaemonError on receiving an IOError from chdir. """
904fc46f261SDan Klein        args = self.test_args
905fc46f261SDan Klein        test_error = FileNotFoundError("No such directory")
906fc46f261SDan Klein        mock_func_os_chdir.side_effect = test_error
907fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
908fc46f261SDan Klein        exc = self.assertRaises(
909fc46f261SDan Klein                expected_error,
910fc46f261SDan Klein                daemon.daemon.change_root_directory, **args)
911fc46f261SDan Klein        self.assertEqual(test_error, exc.__cause__)
912fc46f261SDan Klein
913fc46f261SDan Klein    def test_raises_daemon_error_on_os_error_from_chroot(
914fc46f261SDan Klein            self,
915fc46f261SDan Klein            mock_func_os_chdir, mock_func_os_chroot):
916fc46f261SDan Klein        """ Should raise a DaemonError on receiving an OSError from chroot. """
917fc46f261SDan Klein        args = self.test_args
918fc46f261SDan Klein        test_error = OSError(errno.EPERM, "No chroot for you!")
919fc46f261SDan Klein        mock_func_os_chroot.side_effect = test_error
920fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
921fc46f261SDan Klein        exc = self.assertRaises(
922fc46f261SDan Klein                expected_error,
923fc46f261SDan Klein                daemon.daemon.change_root_directory, **args)
924fc46f261SDan Klein        self.assertEqual(test_error, exc.__cause__)
925fc46f261SDan Klein
926fc46f261SDan Klein    def test_error_message_contains_original_error_message(
927fc46f261SDan Klein            self,
928fc46f261SDan Klein            mock_func_os_chdir, mock_func_os_chroot):
929fc46f261SDan Klein        """ Should raise a DaemonError with original message. """
930fc46f261SDan Klein        args = self.test_args
931fc46f261SDan Klein        test_error = FileNotFoundError("No such directory")
932fc46f261SDan Klein        mock_func_os_chdir.side_effect = test_error
933fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
934fc46f261SDan Klein        exc = self.assertRaises(
935fc46f261SDan Klein                expected_error,
936fc46f261SDan Klein                daemon.daemon.change_root_directory, **args)
937fc46f261SDan Klein        self.assertIn(unicode(test_error), unicode(exc))
938fc46f261SDan Klein
939fc46f261SDan Klein
940fc46f261SDan Klein@mock.patch.object(os, "umask")
941fc46f261SDan Kleinclass change_file_creation_mask_TestCase(scaffold.TestCase):
942fc46f261SDan Klein    """ Test cases for change_file_creation_mask function. """
943fc46f261SDan Klein
944fc46f261SDan Klein    def setUp(self):
945fc46f261SDan Klein        """ Set up test fixtures. """
946fc46f261SDan Klein        super(change_file_creation_mask_TestCase, self).setUp()
947fc46f261SDan Klein
948fc46f261SDan Klein        self.test_mask = object()
949fc46f261SDan Klein        self.test_args = dict(
950fc46f261SDan Klein                mask=self.test_mask,
951fc46f261SDan Klein                )
952fc46f261SDan Klein
953fc46f261SDan Klein    def test_changes_umask_to_specified_mask(self, mock_func_os_umask):
954fc46f261SDan Klein        """ Should change working directory to specified directory. """
955fc46f261SDan Klein        args = self.test_args
956fc46f261SDan Klein        mask = self.test_mask
957fc46f261SDan Klein        daemon.daemon.change_file_creation_mask(**args)
958fc46f261SDan Klein        mock_func_os_umask.assert_called_with(mask)
959fc46f261SDan Klein
960fc46f261SDan Klein    def test_raises_daemon_error_on_os_error_from_chdir(
961fc46f261SDan Klein            self,
962fc46f261SDan Klein            mock_func_os_umask):
963fc46f261SDan Klein        """ Should raise a DaemonError on receiving an OSError from umask. """
964fc46f261SDan Klein        args = self.test_args
965fc46f261SDan Klein        test_error = OSError(errno.EINVAL, "Whatchoo talkin' 'bout?")
966fc46f261SDan Klein        mock_func_os_umask.side_effect = test_error
967fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
968fc46f261SDan Klein        exc = self.assertRaises(
969fc46f261SDan Klein                expected_error,
970fc46f261SDan Klein                daemon.daemon.change_file_creation_mask, **args)
971fc46f261SDan Klein        self.assertEqual(test_error, exc.__cause__)
972fc46f261SDan Klein
973fc46f261SDan Klein    def test_error_message_contains_original_error_message(
974fc46f261SDan Klein            self,
975fc46f261SDan Klein            mock_func_os_umask):
976fc46f261SDan Klein        """ Should raise a DaemonError with original message. """
977fc46f261SDan Klein        args = self.test_args
978fc46f261SDan Klein        test_error = FileNotFoundError("No such directory")
979fc46f261SDan Klein        mock_func_os_umask.side_effect = test_error
980fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
981fc46f261SDan Klein        exc = self.assertRaises(
982fc46f261SDan Klein                expected_error,
983fc46f261SDan Klein                daemon.daemon.change_file_creation_mask, **args)
984fc46f261SDan Klein        self.assertIn(unicode(test_error), unicode(exc))
985fc46f261SDan Klein
986fc46f261SDan Klein
987fc46f261SDan Klein@mock.patch.object(os, "setgid")
988fc46f261SDan Klein@mock.patch.object(os, "setuid")
989fc46f261SDan Kleinclass change_process_owner_TestCase(scaffold.TestCase):
990fc46f261SDan Klein    """ Test cases for change_process_owner function. """
991fc46f261SDan Klein
992fc46f261SDan Klein    def setUp(self):
993fc46f261SDan Klein        """ Set up test fixtures. """
994fc46f261SDan Klein        super(change_process_owner_TestCase, self).setUp()
995fc46f261SDan Klein
996fc46f261SDan Klein        self.test_uid = object()
997fc46f261SDan Klein        self.test_gid = object()
998fc46f261SDan Klein        self.test_args = dict(
999fc46f261SDan Klein                uid=self.test_uid,
1000fc46f261SDan Klein                gid=self.test_gid,
1001fc46f261SDan Klein                )
1002fc46f261SDan Klein
1003fc46f261SDan Klein    def test_changes_gid_and_uid_in_order(
1004fc46f261SDan Klein            self,
1005fc46f261SDan Klein            mock_func_os_setuid, mock_func_os_setgid):
1006fc46f261SDan Klein        """ Should change process GID and UID in correct order.
1007fc46f261SDan Klein
1008fc46f261SDan Klein            Since the process requires appropriate privilege to use
1009fc46f261SDan Klein            either of `setuid` or `setgid`, changing the UID must be
1010fc46f261SDan Klein            done last.
1011fc46f261SDan Klein
1012fc46f261SDan Klein            """
1013fc46f261SDan Klein        args = self.test_args
1014fc46f261SDan Klein        daemon.daemon.change_process_owner(**args)
1015fc46f261SDan Klein        mock_func_os_setuid.assert_called()
1016fc46f261SDan Klein        mock_func_os_setgid.assert_called()
1017fc46f261SDan Klein
1018fc46f261SDan Klein    def test_changes_group_id_to_gid(
1019fc46f261SDan Klein            self,
1020fc46f261SDan Klein            mock_func_os_setuid, mock_func_os_setgid):
1021fc46f261SDan Klein        """ Should change process GID to specified value. """
1022fc46f261SDan Klein        args = self.test_args
1023fc46f261SDan Klein        gid = self.test_gid
1024fc46f261SDan Klein        daemon.daemon.change_process_owner(**args)
1025fc46f261SDan Klein        mock_func_os_setgid.assert_called(gid)
1026fc46f261SDan Klein
1027fc46f261SDan Klein    def test_changes_user_id_to_uid(
1028fc46f261SDan Klein            self,
1029fc46f261SDan Klein            mock_func_os_setuid, mock_func_os_setgid):
1030fc46f261SDan Klein        """ Should change process UID to specified value. """
1031fc46f261SDan Klein        args = self.test_args
1032fc46f261SDan Klein        uid = self.test_uid
1033fc46f261SDan Klein        daemon.daemon.change_process_owner(**args)
1034fc46f261SDan Klein        mock_func_os_setuid.assert_called(uid)
1035fc46f261SDan Klein
1036fc46f261SDan Klein    def test_raises_daemon_error_on_os_error_from_setgid(
1037fc46f261SDan Klein            self,
1038fc46f261SDan Klein            mock_func_os_setuid, mock_func_os_setgid):
1039fc46f261SDan Klein        """ Should raise a DaemonError on receiving an OSError from setgid. """
1040fc46f261SDan Klein        args = self.test_args
1041fc46f261SDan Klein        test_error = OSError(errno.EPERM, "No switching for you!")
1042fc46f261SDan Klein        mock_func_os_setgid.side_effect = test_error
1043fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
1044fc46f261SDan Klein        exc = self.assertRaises(
1045fc46f261SDan Klein                expected_error,
1046fc46f261SDan Klein                daemon.daemon.change_process_owner, **args)
1047fc46f261SDan Klein        self.assertEqual(test_error, exc.__cause__)
1048fc46f261SDan Klein
1049fc46f261SDan Klein    def test_raises_daemon_error_on_os_error_from_setuid(
1050fc46f261SDan Klein            self,
1051fc46f261SDan Klein            mock_func_os_setuid, mock_func_os_setgid):
1052fc46f261SDan Klein        """ Should raise a DaemonError on receiving an OSError from setuid. """
1053fc46f261SDan Klein        args = self.test_args
1054fc46f261SDan Klein        test_error = OSError(errno.EPERM, "No switching for you!")
1055fc46f261SDan Klein        mock_func_os_setuid.side_effect = test_error
1056fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
1057fc46f261SDan Klein        exc = self.assertRaises(
1058fc46f261SDan Klein                expected_error,
1059fc46f261SDan Klein                daemon.daemon.change_process_owner, **args)
1060fc46f261SDan Klein        self.assertEqual(test_error, exc.__cause__)
1061fc46f261SDan Klein
1062fc46f261SDan Klein    def test_error_message_contains_original_error_message(
1063fc46f261SDan Klein            self,
1064fc46f261SDan Klein            mock_func_os_setuid, mock_func_os_setgid):
1065fc46f261SDan Klein        """ Should raise a DaemonError with original message. """
1066fc46f261SDan Klein        args = self.test_args
1067fc46f261SDan Klein        test_error = OSError(errno.EINVAL, "Whatchoo talkin' 'bout?")
1068fc46f261SDan Klein        mock_func_os_setuid.side_effect = test_error
1069fc46f261SDan Klein        expected_error = daemon.daemon.DaemonOSEnvironmentError
1070fc46f261SDan Klein        exc = self.assertRaises(
1071fc46f261SDan Klein                expected_error,
1072fc46f261SDan Klein                daemon.daemon.change_process_owner, **args)
1073fc46f261SDan Klein        self.assertIn(unicode(test_error), unicode(exc))
1074fc46f261SDan Klein
1075fc46f261SDan Klein
1076fc46f261SDan KleinRLimitResult = collections.namedtuple('RLimitResult', ['soft', 'hard'])
1077