Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
P
Pyzenkit
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
713
Mentat
Pyzenkit
Commits
f9d40ce1
Commit
f9d40ce1
authored
7 years ago
by
Honza Mach
Browse files
Options
Downloads
Patches
Plain Diff
Revised documentation and coding style in daemonizer.py library.
parent
cce31eea
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
pyzenkit/daemonizer.py
+182
-93
182 additions, 93 deletions
pyzenkit/daemonizer.py
pyzenkit/tests/test_daemonizer.py
+15
-13
15 additions, 13 deletions
pyzenkit/tests/test_daemonizer.py
with
197 additions
and
106 deletions
pyzenkit/daemonizer.py
+
182
−
93
View file @
f9d40ce1
#!/usr/bin/python
#!/usr/bin/
env
python
3
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Copyright (C) since 2016 Jan Mach <honza.mach.ml@gmail.com>
# Copyright (C) since 2016 Jan Mach <honza.mach.ml@gmail.com>
...
@@ -6,16 +6,39 @@
...
@@ -6,16 +6,39 @@
# Use of this source is governed by the MIT license, see LICENSE file.
# Use of this source is governed by the MIT license, see LICENSE file.
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
"""
"""
Daemonization library.
This module contains simple daemonization library, that takes care of all tasks
necessary to correctly daemonize a process. Correct daemonization consists of
following steps:
* Setup directories and limits
* Setup user and group permissions
* Double fork and split session
* Setup signal handlers
* Close all open file descriptors (except for possible log files)
* Redirect ``stdin``, ``stdout``, ``stderr`` to ``/dev/null``
* Detect current PID and store it to appropriate PID file
* At exit remove PID file
These steps are performed during *full* daemonization. For many purposes it is however
usefull to be capable of some kind of *lite* daemonization, in which case almost
every step in previous list is done except for double forking, closing all files
and redirecting ``std*`` to ``/dev/null``. This can be usefull during testing or debugging,
or even during production deployments for some kind of *stay in foreground* feature, which
still enables the users to control the application from outside with signals.
"""
"""
__author__
=
"
Jan Mach <honza.mach.ml@gmail.com>
"
__credits__
=
"
Pavel Kácha <ph@rook.cz>
"
import
os
import
os
import
re
import
signal
import
signal
import
resource
import
atexit
import
atexit
def
get_logger_files
(
logger
):
def
get_logger_files
(
logger
):
"""
"""
Return file handlers of all currently active loggers.
Return file handlers of all currently active loggers.
...
@@ -25,8 +48,12 @@ def get_logger_files(logger):
...
@@ -25,8 +48,12 @@ def get_logger_files(logger):
.. warning::
.. warning::
This method is hacking internal structure of
different
module and might
This method is hacking internal structure of
external
module and might
stop working, although the related interface has been stable for a long time.
stop working, although the related interface has been stable for a long time.
:param logging.Logger logger: Logger to be analyzed for open file descriptors.
:return: List of open file descriptors used by logging service.
:rtype: list
"""
"""
files
=
[]
files
=
[]
for
handler
in
logger
.
handlers
:
for
handler
in
logger
.
handlers
:
...
@@ -36,75 +63,142 @@ def get_logger_files(logger):
...
@@ -36,75 +63,142 @@ def get_logger_files(logger):
files
.
append
(
handler
.
socket
)
files
.
append
(
handler
.
socket
)
return
files
return
files
def
write_pid
(
pidfile
,
pid
):
def
write_pid
(
pid
_
file
,
pid
):
"""
"""
Write given PID into given PID file.
Write given PID into given PID file.
:param str pidfile: Name of the PID file to write to.
:param int pid: PID to write.
"""
"""
if
not
isinstance
(
pid
,
int
):
if
not
isinstance
(
pid
,
int
):
raise
Exception
(
"
Process PID must be integer
"
)
raise
Exception
(
"
Process PID must be integer
"
)
pidfd
=
os
.
open
(
pidfile
,
os
.
O_RDWR
|
os
.
O_CREAT
|
os
.
O_EXCL
|
os
.
O_TRUNC
)
pidfd
=
os
.
open
(
pid
_
file
,
os
.
O_RDWR
|
os
.
O_CREAT
|
os
.
O_EXCL
|
os
.
O_TRUNC
)
os
.
write
(
pidfd
,
bytes
(
str
(
pid
)
+
"
\n
"
,
'
UTF-8
'
))
os
.
write
(
pidfd
,
bytes
(
str
(
pid
)
+
"
\n
"
,
'
UTF-8
'
))
os
.
close
(
pidfd
)
os
.
close
(
pidfd
)
def
read_pid
(
pidfile
):
def
read_pid
(
pid
_
file
):
"""
"""
Read PID from given PID file.
Read PID from given PID file.
:param str pidfile: Name of the PID file to read from.
:return: PID from given PID file.
:rtype: int
"""
"""
with
open
(
pidfile
,
'
r
'
)
as
pidfd
:
with
open
(
pid
_
file
,
'
r
'
)
as
pidfd
:
return
int
(
pidfd
.
readline
().
strip
())
return
int
(
pidfd
.
readline
().
strip
())
def
daemonize_lite
(
chroot_dir
=
None
,
work_dir
=
None
,
umask
=
None
,
uid
=
None
,
gid
=
None
,
#-------------------------------------------------------------------------------
pidfile
=
None
,
signals
=
{}):
def
_setup_fs
(
chroot_dir
,
work_dir
,
umask
):
"""
"""
Perform lite daemonization of currently running proces
s.
Internal helper method, setup filesystem related feature
s.
The lite daemonization does everything full daemonization does but detaching
:param str chroot_dir: Name of the chroot directory (may be ``None``).
from current session. This can be usefull when debugging daemons, because they
:param str work_dir: Name of the work directory (may be ``None``).
can be tested, benchmarked and profiled more easily
.
:param int umask: Umask as octal number (eg. ``0o002`` or ``0o022``, may be ``None``)
.
"""
"""
# Setup directories, limits, users, etc.
if
chroot_dir
is
not
None
:
if
chroot_dir
is
not
None
:
os
.
chdir
(
chroot_dir
)
os
.
chdir
(
chroot_dir
)
os
.
chroot
(
chroot_dir
)
os
.
chroot
(
chroot_dir
)
if
umask
is
not
None
:
os
.
umask
(
umask
)
if
work_dir
is
not
None
:
if
work_dir
is
not
None
:
os
.
chdir
(
work_dir
)
os
.
chdir
(
work_dir
)
if
umask
is
not
None
:
os
.
umask
(
umask
)
def
_setup_perms
(
uid
,
gid
):
"""
Internal helper method, setup user and group permissions.
:param int uid: User ID to which to drop the permissions (may be ``None``).
:param int gid: Group ID to which to drop the permissions (may be ``None``).
"""
if
gid
is
not
None
:
if
gid
is
not
None
:
os
.
setgid
(
gid
)
os
.
setgid
(
gid
)
if
uid
is
not
None
:
if
uid
is
not
None
:
os
.
setuid
(
uid
)
os
.
setuid
(
uid
)
# Setup signal handlers
def
_setup_sh
(
signals
):
for
(
signum
,
handler
)
in
signals
.
items
():
"""
signal
.
signal
(
signum
,
handler
)
Internal helper method, setup desired signal handlers.
:param dict signals: Desired signal to be handled as keys and appropriate handlers as values (may be ``None``).
"""
if
signals
is
not
None
:
for
(
signum
,
handler
)
in
signals
.
items
():
signal
.
signal
(
signum
,
handler
)
# Detect current process PID.
def
_setup_pf
(
pid_file
):
"""
Internal helper method, setup PID file and atexit cleanup callback.
:param str pid_file: Full path to the PID file (may be ``None``).
"""
pid
=
os
.
getpid
()
pid
=
os
.
getpid
()
# Create PID file and ensure its removal after current process is d
one
.
if
pid_file
is
not
N
one
:
if
pidfile
is
not
None
:
if
not
pid
_
file
.
endswith
(
'
.pid
'
)
:
if
not
pidfile
.
end
s
with
(
'
.pid
'
)
:
raise
ValueError
(
"
Invalid PID file name
'
{}
'
, it must
end
with
'
.pid
'
extension
"
.
format
(
pid_file
)
)
raise
Exception
(
"
Invalid PID file name, it must end with
'
.pid
'
extension
"
)
write_pid
(
pidfile
,
pid
)
write_pid
(
pid
_
file
,
pid
)
# Define and setup 'atexit' closure, that will take care of removing pid file
# Define and setup 'atexit' closure, that will take care of removing pid file
@atexit.register
@atexit.register
def
unlink_pidfile
():
def
unlink_pidfile
():
"""
Callback for removing PID file at application exit.
"""
try
:
try
:
os
.
unlink
(
pidfile
)
os
.
unlink
(
pid
_
file
)
except
Exception
:
except
Exception
:
pass
pass
return
(
pid
,
pidfile
)
return
(
pid
,
pid
_
file
)
else
:
else
:
return
(
pid
,
None
)
return
(
pid
,
None
)
#-------------------------------------------------------------------------------
def
daemonize_lite
(
chroot_dir
=
None
,
work_dir
=
None
,
umask
=
None
,
uid
=
None
,
gid
=
None
,
pid_file
=
None
,
signals
=
None
):
"""
Perform lite daemonization of currently running process. All of the function
arguments are optinal, so that it is possible to easily turn on/off almost
any part of daemonization process. For example omitting the ``uid`` and ``gid``
arguments will result in process permissions not to be changed.
The lite daemonization does everything full daemonization does but detaching
from current session. This can be usefull when debugging daemons, because they
can be tested, benchmarked and profiled more easily.
:param str chroot_dir: Name of the chroot directory (may be ``None``).
:param str work_dir: Name of the work directory (may be ``None``).
:param int umask: Umask as octal number (eg. ``0o002`` or ``0o022``, may be ``None``).
:param int uid: User ID to which to drop the permissions (may be ``None``).
:param int gid: Group ID to which to drop the permissions (may be ``None``).
:param str pid_file: Full path to the PID file (may be ``None``).
:param dict signals: Desired signal to be handled as keys and appropriate handlers as values (may be ``None``).
"""
# Setup directories, limits, users, etc.
_setup_fs
(
chroot_dir
,
work_dir
,
umask
)
_setup_perms
(
uid
,
gid
)
# Setup signal handlers.
_setup_sh
(
signals
)
# Write PID into PID file.
return
_setup_pf
(
pid_file
)
def
daemonize
(
def
daemonize
(
chroot_dir
=
None
,
work_dir
=
None
,
umask
=
None
,
uid
=
None
,
gid
=
None
,
chroot_dir
=
None
,
work_dir
=
None
,
umask
=
None
,
uid
=
None
,
gid
=
None
,
pidfile
=
None
,
files_preserve
=
[]
,
signals
=
{}
):
pid
_
file
=
None
,
files_preserve
=
None
,
signals
=
None
):
"""
"""
Perform full daemonization of currently running process.
Perform full daemonization of currently running process. All of the function
arguments are optinal, so that it is possible to easily turn on/off almost
any part of daemonization process. For example omitting the ``uid`` and ``gid``
arguments will result in process permissions not to be changed.
NOTE: It would be possible to call daemonize_lite() method from within this
NOTE: It would be possible to call daemonize_lite() method from within this
method, howewer for readability purposes and to maintain correct ordering
method, howewer for readability purposes and to maintain correct ordering
...
@@ -112,32 +206,34 @@ def daemonize(
...
@@ -112,32 +206,34 @@ def daemonize(
two separate methods with similar contents. It will be necessary to update
two separate methods with similar contents. It will be necessary to update
both when making any improvements, however I do not expect them to change
both when making any improvements, however I do not expect them to change
much and often, if ever.
much and often, if ever.
:param str chroot_dir: Name of the chroot directory (may be ``None``).
:param str work_dir: Name of the work directory (may be ``None``).
:param int umask: Umask as octal number (eg. ``0o002`` or ``0o022``, may be ``None``).
:param int uid: User ID to which to drop the permissions (may be ``None``).
:param int gid: Group ID to which to drop the permissions (may be ``None``).
:param str pid_file: Full path to the PID file (may be ``None``).
:param list files_preserve: List of file handles to preserve from closing (may be ``None``).
:param dict signals: Desired signal to be handled as keys and appropriate handlers as values (may be ``None``).
"""
"""
# Setup directories, limits, users, etc.
# Setup directories, limits, users, etc.
if
chroot_dir
is
not
None
:
_setup_fs
(
chroot_dir
,
work_dir
,
umask
)
os
.
chdir
(
chroot_dir
)
_setup_perms
(
uid
,
gid
)
os
.
chroot
(
chroot_dir
)
if
umask
is
not
None
:
os
.
umask
(
umask
)
if
work_dir
is
not
None
:
os
.
chdir
(
work_dir
)
if
gid
is
not
None
:
os
.
setgid
(
gid
)
if
uid
is
not
None
:
os
.
setuid
(
uid
)
# Doublefork and split session.
# Doublefork and split session
to fully detach from current terminal
.
if
os
.
fork
()
>
0
:
if
os
.
fork
()
>
0
:
os
.
_exit
(
0
)
os
.
_exit
(
0
)
os
.
setsid
()
os
.
setsid
()
if
os
.
fork
()
>
0
:
if
os
.
fork
()
>
0
:
os
.
_exit
(
0
)
os
.
_exit
(
0
)
# Setup signal handlers
# Setup signal handlers.
for
(
signum
,
handler
)
in
signals
.
items
():
_setup_sh
(
signals
)
signal
.
signal
(
signum
,
handler
)
# Close all open file descriptors.
# Close all open file descriptors, except excluded files.
#if files_preserve is None:
# files_preserve = []
#descr_preserve = set(f.fileno() for f in files_preserve)
#descr_preserve = set(f.fileno() for f in files_preserve)
#maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
#maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
#if maxfd==resource.RLIM_INFINITY:
#if maxfd==resource.RLIM_INFINITY:
...
@@ -151,61 +247,54 @@ def daemonize(
...
@@ -151,61 +247,54 @@ def daemonize(
# Redirect stdin, stdout, stderr to /dev/null.
# Redirect stdin, stdout, stderr to /dev/null.
devnull
=
os
.
open
(
os
.
devnull
,
os
.
O_RDWR
)
devnull
=
os
.
open
(
os
.
devnull
,
os
.
O_RDWR
)
for
fd
in
range
(
3
):
for
fd
n
in
range
(
3
):
os
.
dup2
(
devnull
,
fd
)
os
.
dup2
(
devnull
,
fd
n
)
#
Detect current process PID
.
#
Write PID into PID file
.
pid
=
os
.
getpid
(
)
return
_setup_pf
(
pid_file
)
# Create PID file and ensure its removal after current process is done.
if
pidfile
is
not
None
:
if
not
pidfile
.
endswith
(
'
.pid
'
):
raise
Exception
(
"
Invalid PID file name, it must end with
'
.pid
'
extension
"
)
write_pid
(
pidfile
,
pid
)
# Define and setup atexit closure
#-------------------------------------------------------------------------------
@atexit.register
def
unlink_pidfile
():
try
:
os
.
unlink
(
pidfile
)
except
Exception
:
pass
return
(
pid
,
pidfile
)
else
:
return
(
pid
,
None
)
#
# Perform the demonstration.
#
if
__name__
==
"
__main__
"
:
if
__name__
==
"
__main__
"
:
"""
Perform the demonstration.
"""
def
hnd_sig_hup
(
signum
,
frame
):
def
hnd_sig_hup
(
signum
,
frame
):
print
(
"
Received signal HUP
"
)
"""
Bogus handler for signal HUP for demonstration purposes.
"""
print
(
"
HANDLER CALLBACK: Received signal HUP ({})
"
.
format
(
signum
))
def
hnd_sig_usr1
(
signum
,
frame
):
def
hnd_sig_usr1
(
signum
,
frame
):
print
(
"
Received signal USR1
"
)
"""
Bogus handler for signal USR1 for demonstration purposes.
"""
print
(
"
HANDLER CALLBACK: Received signal USR1 ({})
"
.
format
(
signum
))
def
hnd_sig_usr2
(
signum
,
frame
):
def
hnd_sig_usr2
(
signum
,
frame
):
print
(
"
Received signal USR2
"
)
"""
Bogus handler for signal USR2 for demonstration purposes.
"""
print
(
"
HANDLER CALLBACK: Received signal USR2 ({})
"
.
format
(
signum
))
(
pid
,
pidfile
)
=
daemonize_lite
(
work_dir
=
"
/tmp
"
,
(
PIDV
,
PIDF
)
=
daemonize_lite
(
pidfile
=
"
/tmp/demo.pyzenkit.daemonizer.pid
"
,
work_dir
=
"
/tmp
"
,
signals
=
{
pid_file
=
"
/tmp/demo.pyzenkit.daemonizer.pid
"
,
signal
.
SIGHUP
:
hnd_sig_hup
,
umask
=
0o022
,
signal
.
SIGUSR1
:
hnd_sig_usr1
,
signals
=
{
signal
.
SIGUSR2
:
hnd_sig_usr2
,
signal
.
SIGHUP
:
hnd_sig_hup
,
}
signal
.
SIGUSR1
:
hnd_sig_usr1
,
)
signal
.
SIGUSR2
:
hnd_sig_usr2
,
}
)
print
(
"
Lite daemonization complete:
"
)
print
(
"
Lite daemonization complete:
"
)
print
(
"
\t
PID:
'
{}
'"
.
format
(
pid
))
print
(
"
*
PID:
'
{}
'"
.
format
(
PIDV
))
print
(
"
\t
PID file:
'
{}
'"
.
format
(
pidfile
))
print
(
"
*
PID file:
'
{}
'"
.
format
(
PIDF
))
print
(
"
\t
CWD:
'
{}
'"
.
format
(
os
.
getcwd
()))
print
(
"
*
CWD:
'
{}
'"
.
format
(
os
.
getcwd
()))
print
(
"
\t
PID in PID file:
'
{}
'"
.
format
(
read_pid
(
pidfile
)))
print
(
"
*
PID in PID file:
'
{}
'"
.
format
(
read_pid
(
PIDF
)))
print
(
"
Checking signal handling:
"
)
print
(
"
Checking signal handling:
"
)
os
.
kill
(
pid
,
signal
.
SIGHUP
)
os
.
kill
(
PIDV
,
signal
.
SIGHUP
)
os
.
kill
(
pid
,
signal
.
SIGUSR1
)
os
.
kill
(
PIDV
,
signal
.
SIGUSR1
)
os
.
kill
(
pid
,
signal
.
SIGUSR2
)
os
.
kill
(
PIDV
,
signal
.
SIGUSR2
)
os
.
kill
(
read_pid
(
pidfile
),
signal
.
SIGHUP
)
print
(
"
Checking signal handling, read PID from PID file:
"
)
os
.
kill
(
read_pid
(
pidfile
),
signal
.
SIGUSR1
)
os
.
kill
(
read_pid
(
PIDF
),
signal
.
SIGHUP
)
os
.
kill
(
read_pid
(
pidfile
),
signal
.
SIGUSR2
)
os
.
kill
(
read_pid
(
PIDF
),
signal
.
SIGUSR1
)
os
.
kill
(
read_pid
(
PIDF
),
signal
.
SIGUSR2
)
This diff is collapsed.
Click to expand it.
pyzenkit/tests/test_daemonizer.py
+
15
−
13
View file @
f9d40ce1
...
@@ -6,6 +6,7 @@
...
@@ -6,6 +6,7 @@
# Use of this source is governed by the MIT license, see LICENSE file.
# Use of this source is governed by the MIT license, see LICENSE file.
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
import
unittest
import
unittest
from
unittest.mock
import
Mock
,
MagicMock
,
call
from
unittest.mock
import
Mock
,
MagicMock
,
call
from
pprint
import
pformat
,
pprint
from
pprint
import
pformat
,
pprint
...
@@ -15,20 +16,18 @@ import sys
...
@@ -15,20 +16,18 @@ import sys
import
shutil
import
shutil
import
signal
import
signal
# Generate the path to custom 'lib' directory
# Generate the path to custom 'lib' directory
lib
=
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'
../../
'
))
lib
=
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'
../../
'
))
sys
.
path
.
insert
(
0
,
lib
)
sys
.
path
.
insert
(
0
,
lib
)
import
pyzenkit.daemonizer
import
pyzenkit.daemonizer
PID_FILE
=
'
/tmp/test.pyzenkit.daemonizer.pid
'
PID_FILE
=
'
/tmp/test.pyzenkit.daemonizer.pid
'
class
TestPyzenkitDaemonizer
(
unittest
.
TestCase
):
def
setUp
(
self
):
class
TestPyzenkitDaemonizer
(
unittest
.
TestCase
):
pass
def
tearDown
(
self
):
pass
def
test_01_basic
(
self
):
def
test_01_basic
(
self
):
"""
"""
...
@@ -49,26 +48,29 @@ class TestPyzenkitDaemonizer(unittest.TestCase):
...
@@ -49,26 +48,29 @@ class TestPyzenkitDaemonizer(unittest.TestCase):
Perform lite daemonization tests.
Perform lite daemonization tests.
"""
"""
def
hnd_sig_hup
(
signum
,
frame
):
def
hnd_sig_hup
(
signum
,
frame
):
print
(
"
Received signal HUP
"
)
print
(
"
HANDLER CALLBACK: Received signal HUP ({})
"
.
format
(
signum
))
def
hnd_sig_usr1
(
signum
,
frame
):
def
hnd_sig_usr1
(
signum
,
frame
):
print
(
"
Received signal USR1
"
)
print
(
"
HANDLER CALLBACK: Received signal USR1 ({})
"
.
format
(
signum
))
def
hnd_sig_usr2
(
signum
,
frame
):
def
hnd_sig_usr2
(
signum
,
frame
):
print
(
"
Received signal USR2
"
)
print
(
"
HANDLER CALLBACK:
Received signal USR2
({})
"
.
format
(
signum
)
)
self
.
assertFalse
(
os
.
path
.
isfile
(
PID_FILE
))
self
.
assertFalse
(
os
.
path
.
isfile
(
PID_FILE
))
(
pid
,
pidfile
)
=
pyzenkit
.
daemonizer
.
daemonize_lite
(
(
pid
,
pid
_
file
)
=
pyzenkit
.
daemonizer
.
daemonize_lite
(
work_dir
=
'
/tmp
'
,
work_dir
=
'
/tmp
'
,
pidfile
=
PID_FILE
,
pid_file
=
PID_FILE
,
signals
=
{
umask
=
0o022
,
signals
=
{
signal
.
SIGHUP
:
hnd_sig_hup
,
signal
.
SIGHUP
:
hnd_sig_hup
,
signal
.
SIGUSR1
:
hnd_sig_usr1
,
signal
.
SIGUSR1
:
hnd_sig_usr1
,
signal
.
SIGUSR2
:
hnd_sig_usr2
,
signal
.
SIGUSR2
:
hnd_sig_usr2
,
},
},
)
)
self
.
assertTrue
(
os
.
path
.
isfile
(
PID_FILE
))
self
.
assertTrue
(
os
.
path
.
isfile
(
PID_FILE
))
self
.
assertTrue
(
os
.
path
.
isfile
(
pidfile
))
self
.
assertTrue
(
os
.
path
.
isfile
(
pid
_
file
))
self
.
assertEqual
(
pyzenkit
.
daemonizer
.
read_pid
(
PID_FILE
),
pid
)
self
.
assertEqual
(
pyzenkit
.
daemonizer
.
read_pid
(
PID_FILE
),
pid
)
self
.
assertEqual
(
pyzenkit
.
daemonizer
.
read_pid
(
pidfile
),
pid
)
self
.
assertEqual
(
pyzenkit
.
daemonizer
.
read_pid
(
pid
_
file
),
pid
)
self
.
assertEqual
(
os
.
getcwd
(),
'
/tmp
'
)
self
.
assertEqual
(
os
.
getcwd
(),
'
/tmp
'
)
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment