summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2015-10-25 16:51:04 -0400
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2015-10-25 16:51:04 -0400
commit035700c2b585af0821e1ef9e3c389586c73ade17 (patch)
treeddc605a7a05543520648df74c5f511aba7548dd0
parent2e3e9748d46fef423bae36bd4f5d94e38f66f85c (diff)
parent1d8f5f26dfeeacfd13ec2bb2783a5638d3eb1c13 (diff)
downloadpython-systemd-035700c2b585af0821e1ef9e3c389586c73ade17.tar.gz
Merge branch 'tests'
-rw-r--r--docs/journal.rst38
-rw-r--r--docs/login.rst6
-rw-r--r--pytest.ini3
-rw-r--r--systemd/journal.py333
-rw-r--r--systemd/test/test_daemon.py198
-rw-r--r--systemd/test/test_journal.py105
6 files changed, 513 insertions, 170 deletions
diff --git a/docs/journal.rst b/docs/journal.rst
index ea74cf8..8e4b5b6 100644
--- a/docs/journal.rst
+++ b/docs/journal.rst
@@ -41,11 +41,45 @@ event loop:
>>> from systemd import journal
>>> j = journal.Reader()
>>> j.seek_tail()
+ >>> journal.send('testing 1,2,3') # make sure we have something to read
+ >>> j.add_match('MESSAGE=testing 1,2,3')
>>> p = select.poll()
>>> p.register(j, j.get_events())
- >>> p.poll()
+ >>> p.poll() # doctest: +SKIP
[(3, 1)]
- >>> j.get_next()
+ >>> j.get_next() # doctest: +SKIP
+ {'_AUDIT_LOGINUID': 1000,
+ '_CAP_EFFECTIVE': '0',
+ '_SELINUX_CONTEXT': 'unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023',
+ '_GID': 1000,
+ 'CODE_LINE': 1,
+ '_HOSTNAME': '...',
+ '_SYSTEMD_SESSION': 52,
+ '_SYSTEMD_OWNER_UID': 1000,
+ 'MESSAGE': 'testing 1,2,3',
+ '__MONOTONIC_TIMESTAMP':
+ journal.Monotonic(timestamp=datetime.timedelta(2, 76200, 811585),
+ bootid=UUID('958b7e26-df4c-453a-a0f9-a8406cb508f2')),
+ 'SYSLOG_IDENTIFIER': 'python3',
+ '_UID': 1000,
+ '_EXE': '/usr/bin/python3',
+ '_PID': 7733,
+ '_COMM': '...',
+ 'CODE_FUNC': '<module>',
+ 'CODE_FILE': '<doctest journal.rst[4]>',
+ '_SOURCE_REALTIME_TIMESTAMP':
+ datetime.datetime(2015, 9, 5, 13, 17, 4, 944355),
+ '__CURSOR': 's=...',
+ '_BOOT_ID': UUID('958b7e26-df4c-453a-a0f9-a8406cb508f2'),
+ '_CMDLINE': '/usr/bin/python3 ...',
+ '_MACHINE_ID': UUID('263bb31e-3e13-4062-9bdb-f1f4518999d2'),
+ '_SYSTEMD_SLICE': 'user-1000.slice',
+ '_AUDIT_SESSION': 52,
+ '__REALTIME_TIMESTAMP': datetime.datetime(2015, 9, 5, 13, 17, 4, 945110),
+ '_SYSTEMD_UNIT': 'session-52.scope',
+ '_SYSTEMD_CGROUP': '/user.slice/user-1000.slice/session-52.scope',
+ '_TRANSPORT': 'journal'}
+
Journal access types
diff --git a/docs/login.rst b/docs/login.rst
index 6b4de64..2ee807c 100644
--- a/docs/login.rst
+++ b/docs/login.rst
@@ -20,9 +20,9 @@ external event loop:
>>> m = login.Monitor("machine")
>>> p = select.poll()
>>> p.register(m, m.get_events())
- >>> login.machine_names()
+ >>> login.machine_names() # doctest: +SKIP
[]
- >>> p.poll()
+ >>> p.poll() # doctest: +SKIP
[(3, 1)]
- >>> login.machine_names()
+ >>> login.machine_names() # doctest: +SKIP
['fedora-19.nspawn']
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..c5beb19
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+addopts = --doctest-modules --doctest-glob=*.rst --ignore=setup.py
+norecursedirs = .git build
diff --git a/systemd/journal.py b/systemd/journal.py
index 53bab0e..b319a8a 100644
--- a/systemd/journal.py
+++ b/systemd/journal.py
@@ -100,10 +100,10 @@ DEFAULT_CONVERTERS = {
'COREDUMP_TIMESTAMP': _convert_timestamp,
}
-_IDENT_LETTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_')
+_IDENT_CHARACTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_0123456789')
def _valid_field_name(s):
- return not (set(s) - _IDENT_LETTER)
+ return not (set(s) - _IDENT_CHARACTER)
class Reader(_Reader):
"""Reader allows the access and filtering of systemd journal
@@ -113,12 +113,14 @@ class Reader(_Reader):
Example usage to print out all informational or higher level
messages for systemd-udevd for this boot:
+ >>> from systemd import journal
>>> j = journal.Reader()
>>> j.this_boot()
>>> j.log_level(journal.LOG_INFO)
>>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
- >>> for entry in j:
+ >>> for entry in j: # doctest: +SKIP
... print(entry['MESSAGE'])
+ starting version ...
See systemd.journal-fields(7) for more info on typical fields
found in the journal.
@@ -176,7 +178,7 @@ class Reader(_Reader):
return value
def _convert_entry(self, entry):
- """Convert entire journal entry utilising _covert_field"""
+ """Convert entire journal entry utilising _convert_field"""
result = {}
for key, value in entry.items():
if isinstance(value, list):
@@ -229,9 +231,9 @@ class Reader(_Reader):
if super(Reader, self)._next(skip):
entry = super(Reader, self)._get_all()
if entry:
- entry['__REALTIME_TIMESTAMP'] = self._get_realtime()
- entry['__MONOTONIC_TIMESTAMP'] = self._get_monotonic()
- entry['__CURSOR'] = self._get_cursor()
+ entry['__REALTIME_TIMESTAMP'] = self._get_realtime()
+ entry['__MONOTONIC_TIMESTAMP'] = self._get_monotonic()
+ entry['__CURSOR'] = self._get_cursor()
return self._convert_entry(entry)
return dict()
@@ -257,7 +259,7 @@ class Reader(_Reader):
Reader creation.
"""
return set(self._convert_field(field, value)
- for value in super(Reader, self).query_unique(field))
+ for value in super(Reader, self).query_unique(field))
def wait(self, timeout=None):
"""Wait for a change in the journal. `timeout` is the maximum
@@ -317,9 +319,11 @@ class Reader(_Reader):
self.add_match(MESSAGE_ID=messageid)
def this_boot(self, bootid=None):
- """Add match for _BOOT_ID equal to current boot ID or the specified boot ID.
+ """Add match for _BOOT_ID equal to current boot ID or the specified
+ boot ID.
- If specified, bootid should be either a UUID or a 32 digit hex number.
+ If specified, bootid should be either a UUID or a 32 digit hex
+ number.
Equivalent to add_match(_BOOT_ID='bootid').
"""
@@ -332,7 +336,8 @@ class Reader(_Reader):
def this_machine(self, machineid=None):
"""Add match for _MACHINE_ID equal to the ID of this machine.
- If specified, machineid should be either a UUID or a 32 digit hex number.
+ If specified, machineid should be either a UUID or a 32 digit
+ hex number.
Equivalent to add_match(_MACHINE_ID='machineid').
"""
@@ -349,199 +354,197 @@ def get_catalog(mid):
return _get_catalog(mid)
def _make_line(field, value):
- if isinstance(value, bytes):
- return field.encode('utf-8') + b'=' + value
- elif isinstance(value, int):
- return field + '=' + str(value)
- else:
- return field + '=' + value
+ if isinstance(value, bytes):
+ return field.encode('utf-8') + b'=' + value
+ elif isinstance(value, int):
+ return field + '=' + str(value)
+ else:
+ return field + '=' + value
def send(MESSAGE, MESSAGE_ID=None,
CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
**kwargs):
- r"""Send a message to the journal.
+ r"""Send a message to the journal.
- >>> journal.send('Hello world')
- >>> journal.send('Hello, again, world', FIELD2='Greetings!')
- >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
+ >>> from systemd import journal
+ >>> journal.send('Hello world')
+ >>> journal.send('Hello, again, world', FIELD2='Greetings!')
+ >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
- Value of the MESSAGE argument will be used for the MESSAGE=
- field. MESSAGE must be a string and will be sent as UTF-8 to
- the journal.
+ Value of the MESSAGE argument will be used for the MESSAGE=
+ field. MESSAGE must be a string and will be sent as UTF-8 to the
+ journal.
- MESSAGE_ID can be given to uniquely identify the type of
- message. It must be a string or a uuid.UUID object.
+ MESSAGE_ID can be given to uniquely identify the type of
+ message. It must be a string or a uuid.UUID object.
- CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to
- identify the caller. Unless at least on of the three is given,
- values are extracted from the stack frame of the caller of
- send(). CODE_FILE and CODE_FUNC must be strings, CODE_LINE
- must be an integer.
+ CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to identify
+ the caller. Unless at least on of the three is given, values are
+ extracted from the stack frame of the caller of send(). CODE_FILE
+ and CODE_FUNC must be strings, CODE_LINE must be an integer.
- Additional fields for the journal entry can only be specified
- as keyword arguments. The payload can be either a string or
- bytes. A string will be sent as UTF-8, and bytes will be sent
- as-is to the journal.
+ Additional fields for the journal entry can only be specified as
+ keyword arguments. The payload can be either a string or bytes. A
+ string will be sent as UTF-8, and bytes will be sent as-is to the
+ journal.
- Other useful fields include PRIORITY, SYSLOG_FACILITY,
- SYSLOG_IDENTIFIER, SYSLOG_PID.
- """
+ Other useful fields include PRIORITY, SYSLOG_FACILITY,
+ SYSLOG_IDENTIFIER, SYSLOG_PID.
+ """
- args = ['MESSAGE=' + MESSAGE]
+ args = ['MESSAGE=' + MESSAGE]
- if MESSAGE_ID is not None:
- id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID)
- args.append('MESSAGE_ID=' + id)
+ if MESSAGE_ID is not None:
+ id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID)
+ args.append('MESSAGE_ID=' + id)
- if CODE_LINE == CODE_FILE == CODE_FUNC == None:
- CODE_FILE, CODE_LINE, CODE_FUNC = \
- _traceback.extract_stack(limit=2)[0][:3]
- if CODE_FILE is not None:
- args.append('CODE_FILE=' + CODE_FILE)
- if CODE_LINE is not None:
- args.append('CODE_LINE={:d}'.format(CODE_LINE))
- if CODE_FUNC is not None:
- args.append('CODE_FUNC=' + CODE_FUNC)
+ if CODE_LINE == CODE_FILE == CODE_FUNC == None:
+ CODE_FILE, CODE_LINE, CODE_FUNC = _traceback.extract_stack(limit=2)[0][:3]
+ if CODE_FILE is not None:
+ args.append('CODE_FILE=' + CODE_FILE)
+ if CODE_LINE is not None:
+ args.append('CODE_LINE={:d}'.format(CODE_LINE))
+ if CODE_FUNC is not None:
+ args.append('CODE_FUNC=' + CODE_FUNC)
- args.extend(_make_line(key, val) for key, val in kwargs.items())
- return sendv(*args)
+ args.extend(_make_line(key, val) for key, val in kwargs.items())
+ return sendv(*args)
def stream(identifier, priority=LOG_DEBUG, level_prefix=False):
- r"""Return a file object wrapping a stream to journal.
+ r"""Return a file object wrapping a stream to journal.
- Log messages written to this file as simple newline sepearted
- text strings are written to the journal.
+ Log messages written to this file as simple newline sepearted text
+ strings are written to the journal.
- The file will be line buffered, so messages are actually sent
- after a newline character is written.
+ The file will be line buffered, so messages are actually sent
+ after a newline character is written.
- >>> stream = journal.stream('myapp')
- >>> stream
- <open file '<fdopen>', mode 'w' at 0x...>
- >>> stream.write('message...\n')
+ >>> from systemd import journal
+ >>> stream = journal.stream('myapp')
+ >>> res = stream.write('message...\n')
- will produce the following message in the journal::
+ will produce the following message in the journal::
- PRIORITY=7
- SYSLOG_IDENTIFIER=myapp
- MESSAGE=message...
+ PRIORITY=7
+ SYSLOG_IDENTIFIER=myapp
+ MESSAGE=message...
- Using the interface with print might be more convinient:
+ Using the interface with print might be more convinient:
- >>> from __future__ import print_function
- >>> print('message...', file=stream)
+ >>> from __future__ import print_function
+ >>> print('message...', file=stream) # doctest: +SKIP
- priority is the syslog priority, one of `LOG_EMERG`,
- `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`,
- `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`.
+ priority is the syslog priority, one of `LOG_EMERG`,
+ `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`,
+ `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`.
- level_prefix is a boolean. If true, kernel-style log priority
- level prefixes (such as '<1>') are interpreted. See
- sd-daemon(3) for more information.
- """
+ level_prefix is a boolean. If true, kernel-style log priority
+ level prefixes (such as '<1>') are interpreted. See
+ sd-daemon(3) for more information.
+ """
- fd = stream_fd(identifier, priority, level_prefix)
- return _os.fdopen(fd, 'w', 1)
+ fd = stream_fd(identifier, priority, level_prefix)
+ return _os.fdopen(fd, 'w', 1)
class JournalHandler(_logging.Handler):
- """Journal handler class for the Python logging framework.
+ """Journal handler class for the Python logging framework.
- Please see the Python logging module documentation for an
- overview: http://docs.python.org/library/logging.html.
+ Please see the Python logging module documentation for an
+ overview: http://docs.python.org/library/logging.html.
- To create a custom logger whose messages go only to journal:
+ To create a custom logger whose messages go only to journal:
- >>> log = logging.getLogger('custom_logger_name')
- >>> log.propagate = False
- >>> log.addHandler(journal.JournalHandler())
- >>> log.warn("Some message: %s", detail)
+ >>> import logging
+ >>> log = logging.getLogger('custom_logger_name')
+ >>> log.propagate = False
+ >>> log.addHandler(JournalHandler())
+ >>> log.warn("Some message: %s", 'detail')
- Note that by default, message levels `INFO` and `DEBUG` are
- ignored by the logging framework. To enable those log levels:
+ Note that by default, message levels `INFO` and `DEBUG` are
+ ignored by the logging framework. To enable those log levels:
- >>> log.setLevel(logging.DEBUG)
+ >>> log.setLevel(logging.DEBUG)
- To redirect all logging messages to journal regardless of where
- they come from, attach it to the root logger:
+ To redirect all logging messages to journal regardless of where
+ they come from, attach it to the root logger:
- >>> logging.root.addHandler(journal.JournalHandler())
+ >>> logging.root.addHandler(JournalHandler())
- For more complex configurations when using `dictConfig` or
- `fileConfig`, specify `systemd.journal.JournalHandler` as the
- handler class. Only standard handler configuration options
- are supported: `level`, `formatter`, `filters`.
+ For more complex configurations when using `dictConfig` or
+ `fileConfig`, specify `systemd.journal.JournalHandler` as the
+ handler class. Only standard handler configuration options
+ are supported: `level`, `formatter`, `filters`.
- To attach journal MESSAGE_ID, an extra field is supported:
+ To attach journal MESSAGE_ID, an extra field is supported:
- >>> import uuid
- >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF')
- >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid})
+ >>> import uuid
+ >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF')
+ >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid})
- Fields to be attached to all messages sent through this
- handler can be specified as keyword arguments. This probably
- makes sense only for SYSLOG_IDENTIFIER and similar fields
- which are constant for the whole program:
+ Fields to be attached to all messages sent through this handler
+ can be specified as keyword arguments. This probably makes sense
+ only for SYSLOG_IDENTIFIER and similar fields which are constant
+ for the whole program:
- >>> journal.JournalHandler(SYSLOG_IDENTIFIER='my-cool-app')
+ >>> JournalHandler(SYSLOG_IDENTIFIER='my-cool-app')
+ <systemd.journal.JournalHandler object at ...>
- The following journal fields will be sent:
- `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`,
- `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call),
- `MESSAGE_ID` (optional, see above), `SYSLOG_IDENTIFIER` (defaults
- to sys.argv[0]).
- """
+ The following journal fields will be sent: `MESSAGE`, `PRIORITY`,
+ `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`, `CODE_FUNC`, `LOGGER`
+ (name as supplied to getLogger call), `MESSAGE_ID` (optional, see
+ above), `SYSLOG_IDENTIFIER` (defaults to sys.argv[0]).
+ """
- def __init__(self, level=_logging.NOTSET, **kwargs):
- super(JournalHandler, self).__init__(level)
-
- for name in kwargs:
- if not _valid_field_name(name):
- raise ValueError('Invalid field name: ' + name)
- if 'SYSLOG_IDENTIFIER' not in kwargs:
- kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0]
- self._extra = kwargs
-
- def emit(self, record):
- """Write record as journal event.
-
- MESSAGE is taken from the message provided by the
- user, and PRIORITY, LOGGER, THREAD_NAME,
- CODE_{FILE,LINE,FUNC} fields are appended
- automatically. In addition, record.MESSAGE_ID will be
- used if present.
- """
- try:
- msg = self.format(record)
- pri = self.mapPriority(record.levelno)
- mid = getattr(record, 'MESSAGE_ID', None)
- send(msg,
- MESSAGE_ID=mid,
- PRIORITY=format(pri),
- LOGGER=record.name,
- THREAD_NAME=record.threadName,
- CODE_FILE=record.pathname,
- CODE_LINE=record.lineno,
- CODE_FUNC=record.funcName,
- **self._extra)
- except Exception:
- self.handleError(record)
-
- @staticmethod
- def mapPriority(levelno):
- """Map logging levels to journald priorities.
-
- Since Python log level numbers are "sparse", we have
- to map numbers in between the standard levels too.
- """
- if levelno <= _logging.DEBUG:
- return LOG_DEBUG
- elif levelno <= _logging.INFO:
- return LOG_INFO
- elif levelno <= _logging.WARNING:
- return LOG_WARNING
- elif levelno <= _logging.ERROR:
- return LOG_ERR
- elif levelno <= _logging.CRITICAL:
- return LOG_CRIT
- else:
- return LOG_ALERT
+ def __init__(self, level=_logging.NOTSET, **kwargs):
+ super(JournalHandler, self).__init__(level)
+
+ for name in kwargs:
+ if not _valid_field_name(name):
+ raise ValueError('Invalid field name: ' + name)
+ if 'SYSLOG_IDENTIFIER' not in kwargs:
+ kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0]
+ self._extra = kwargs
+
+ def emit(self, record):
+ """Write record as journal event.
+
+ MESSAGE is taken from the message provided by the user, and
+ PRIORITY, LOGGER, THREAD_NAME, CODE_{FILE,LINE,FUNC} fields
+ are appended automatically. In addition, record.MESSAGE_ID
+ will be used if present.
+ """
+ try:
+ msg = self.format(record)
+ pri = self.mapPriority(record.levelno)
+ mid = getattr(record, 'MESSAGE_ID', None)
+ send(msg,
+ MESSAGE_ID=mid,
+ PRIORITY=format(pri),
+ LOGGER=record.name,
+ THREAD_NAME=record.threadName,
+ CODE_FILE=record.pathname,
+ CODE_LINE=record.lineno,
+ CODE_FUNC=record.funcName,
+ **self._extra)
+ except Exception:
+ self.handleError(record)
+
+ @staticmethod
+ def mapPriority(levelno):
+ """Map logging levels to journald priorities.
+
+ Since Python log level numbers are "sparse", we have to map
+ numbers in between the standard levels too.
+ """
+ if levelno <= _logging.DEBUG:
+ return LOG_DEBUG
+ elif levelno <= _logging.INFO:
+ return LOG_INFO
+ elif levelno <= _logging.WARNING:
+ return LOG_WARNING
+ elif levelno <= _logging.ERROR:
+ return LOG_ERR
+ elif levelno <= _logging.CRITICAL:
+ return LOG_CRIT
+ else:
+ return LOG_ALERT
diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py
new file mode 100644
index 0000000..af14f94
--- /dev/null
+++ b/systemd/test/test_daemon.py
@@ -0,0 +1,198 @@
+import sys
+import os
+import posix
+import socket
+import contextlib
+import errno
+from systemd.daemon import (booted,
+ is_fifo, _is_fifo,
+ is_socket, _is_socket,
+ is_socket_inet, _is_socket_inet,
+ is_socket_unix, _is_socket_unix,
+ is_mq, _is_mq,
+ listen_fds)
+
+import pytest
+
+@contextlib.contextmanager
+def closing_socketpair(family):
+ pair = socket.socketpair(family)
+ try:
+ yield pair
+ finally:
+ pair[0].close()
+ pair[1].close()
+
+
+def test_booted():
+ if os.path.exists('/run/systemd'):
+ # assume we are running under systemd
+ assert booted()
+ else:
+ # don't assume anything
+ assert booted() in {False, True}
+
+def test__is_fifo(tmpdir):
+ path = tmpdir.join('test.fifo').strpath
+ posix.mkfifo(path)
+ fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK)
+
+ assert _is_fifo(fd, None)
+ assert _is_fifo(fd, path)
+
+def test__is_fifo_file(tmpdir):
+ file = tmpdir.join('test.fifo')
+ file.write('boo')
+ path = file.strpath
+ fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK)
+
+ assert not _is_fifo(fd, None)
+ assert not _is_fifo(fd, path)
+
+def test__is_fifo_bad_fd(tmpdir):
+ path = tmpdir.join('test.fifo').strpath
+
+ with pytest.raises(OSError):
+ assert not _is_fifo(-1, None)
+
+ with pytest.raises(OSError):
+ assert not _is_fifo(-1, path)
+
+def test_is_fifo(tmpdir):
+ path = tmpdir.join('test.fifo').strpath
+ posix.mkfifo(path)
+ fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK)
+ file = os.fdopen(fd, 'r')
+
+ assert is_fifo(file, None)
+ assert is_fifo(file, path)
+ assert is_fifo(fd, None)
+ assert is_fifo(fd, path)
+
+def test_is_fifo_file(tmpdir):
+ file = tmpdir.join('test.fifo')
+ file.write('boo')
+ path = file.strpath
+ fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK)
+ file = os.fdopen(fd, 'r')
+
+ assert not is_fifo(file, None)
+ assert not is_fifo(file, path)
+ assert not is_fifo(fd, None)
+ assert not is_fifo(fd, path)
+
+def test_is_fifo_bad_fd(tmpdir):
+ path = tmpdir.join('test.fifo').strpath
+
+ with pytest.raises(OSError):
+ assert not is_fifo(-1, None)
+
+ with pytest.raises(OSError):
+ assert not is_fifo(-1, path)
+
+def is_mq_wrapper(arg):
+ try:
+ return is_mq(arg)
+ except OSError as error:
+ # systemd < 227 compatiblity
+ assert error.errno == errno.EBADF
+ return False
+
+def _is_mq_wrapper(arg):
+ try:
+ return _is_mq(arg)
+ except OSError as error:
+ # systemd < 227 compatiblity
+ assert error.errno == errno.EBADF
+ return False
+
+def test_no_mismatch():
+ with closing_socketpair(socket.AF_UNIX) as pair:
+ for sock in pair:
+ assert not is_fifo(sock)
+ assert not is_mq_wrapper(sock)
+ assert not is_socket_inet(sock)
+
+ fd = sock.fileno()
+ assert not is_fifo(fd)
+ assert not is_mq_wrapper(fd)
+ assert not is_socket_inet(fd)
+
+ assert not _is_fifo(fd)
+ assert not _is_mq_wrapper(fd)
+ assert not _is_socket_inet(fd)
+
+def test_is_socket():
+ with closing_socketpair(socket.AF_UNIX) as pair:
+ for sock in pair:
+ for arg in (sock, sock.fileno()):
+ assert is_socket(arg)
+ assert is_socket(arg, socket.AF_UNIX)
+ assert not is_socket(arg, socket.AF_INET)
+ assert is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM)
+ assert not is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM)
+
+ assert is_socket(sock)
+ assert is_socket(arg, socket.AF_UNIX)
+ assert not is_socket(arg, socket.AF_INET)
+ assert is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM)
+ assert not is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM)
+
+def test__is_socket():
+ with closing_socketpair(socket.AF_UNIX) as pair:
+ for sock in pair:
+ fd = sock.fileno()
+ assert _is_socket(fd)
+ assert _is_socket(fd, socket.AF_UNIX)
+ assert not _is_socket(fd, socket.AF_INET)
+ assert _is_socket(fd, socket.AF_UNIX, socket.SOCK_STREAM)
+ assert not _is_socket(fd, socket.AF_INET, socket.SOCK_DGRAM)
+
+ assert _is_socket(fd)
+ assert _is_socket(fd, socket.AF_UNIX)
+ assert not _is_socket(fd, socket.AF_INET)
+ assert _is_socket(fd, socket.AF_UNIX, socket.SOCK_STREAM)
+ assert not _is_socket(fd, socket.AF_INET, socket.SOCK_DGRAM)
+
+def test_is_socket_unix():
+ with closing_socketpair(socket.AF_UNIX) as pair:
+ for sock in pair:
+ for arg in (sock, sock.fileno()):
+ assert is_socket_unix(arg)
+ assert not is_socket_unix(arg, path="/no/such/path")
+ assert is_socket_unix(arg, socket.SOCK_STREAM)
+ assert not is_socket_unix(arg, socket.SOCK_DGRAM)
+
+def test__is_socket_unix():
+ with closing_socketpair(socket.AF_UNIX) as pair:
+ for sock in pair:
+ fd = sock.fileno()
+ assert _is_socket_unix(fd)
+ assert not _is_socket_unix(fd, 0, -1, "/no/such/path")
+ assert _is_socket_unix(fd, socket.SOCK_STREAM)
+ assert not _is_socket_unix(fd, socket.SOCK_DGRAM)
+
+def test_listen_fds_no_fds():
+ # make sure we have no fds to listen to
+ os.unsetenv('LISTEN_FDS')
+ os.unsetenv('LISTEN_PID')
+
+ assert listen_fds() == []
+ assert listen_fds(True) == []
+ assert listen_fds(False) == []
+
+def test_listen_fds():
+ os.environ['LISTEN_FDS'] = '3'
+ os.environ['LISTEN_PID'] = str(os.getpid())
+
+ assert listen_fds(False) == [3, 4, 5]
+ assert listen_fds(True) == [3, 4, 5]
+ assert listen_fds() == []
+
+def test_listen_fds_default_unset():
+ os.environ['LISTEN_FDS'] = '1'
+ os.environ['LISTEN_PID'] = str(os.getpid())
+
+ assert listen_fds(False) == [3]
+ assert listen_fds() == [3]
+ assert listen_fds() == []
diff --git a/systemd/test/test_journal.py b/systemd/test/test_journal.py
new file mode 100644
index 0000000..ab155da
--- /dev/null
+++ b/systemd/test/test_journal.py
@@ -0,0 +1,105 @@
+import logging
+import uuid
+from systemd import journal, id128
+
+import pytest
+
+TEST_MID = uuid.UUID('8441372f8dca4ca98694a6091fd8519f')
+
+def test_priorities():
+ p = journal.JournalHandler.mapPriority
+
+ assert p(logging.NOTSET) == journal.LOG_DEBUG
+ assert p(logging.DEBUG) == journal.LOG_DEBUG
+ assert p(logging.DEBUG - 1) == journal.LOG_DEBUG
+ assert p(logging.DEBUG + 1) == journal.LOG_INFO
+ assert p(logging.INFO - 1) == journal.LOG_INFO
+ assert p(logging.INFO) == journal.LOG_INFO
+ assert p(logging.INFO + 1) == journal.LOG_WARNING
+ assert p(logging.WARN - 1) == journal.LOG_WARNING
+ assert p(logging.WARN) == journal.LOG_WARNING
+ assert p(logging.WARN + 1) == journal.LOG_ERR
+ assert p(logging.ERROR - 1) == journal.LOG_ERR
+ assert p(logging.ERROR) == journal.LOG_ERR
+ assert p(logging.ERROR + 1) == journal.LOG_CRIT
+ assert p(logging.FATAL) == journal.LOG_CRIT
+ assert p(logging.CRITICAL) == journal.LOG_CRIT
+ assert p(logging.CRITICAL + 1) == journal.LOG_ALERT
+
+
+def test_journalhandler_init_exception():
+ kw = {' X ':3}
+ with pytest.raises(ValueError):
+ journal.JournalHandler(**kw)
+
+def test_journalhandler_init():
+ kw = {'X':3, 'X3':4}
+ journal.JournalHandler(logging.INFO, **kw)
+
+def test_reader_init_flags():
+ j1 = journal.Reader()
+ j2 = journal.Reader(journal.LOCAL_ONLY)
+ j3 = journal.Reader(journal.RUNTIME_ONLY)
+ j4 = journal.Reader(journal.SYSTEM_ONLY)
+ j5 = journal.Reader(journal.LOCAL_ONLY|
+ journal.RUNTIME_ONLY|
+ journal.SYSTEM_ONLY)
+ j6 = journal.Reader(0)
+
+def test_reader_init_path(tmpdir):
+ j = journal.Reader(path=tmpdir.strpath)
+ with pytest.raises(ValueError):
+ journal.Reader(journal.LOCAL_ONLY, path=tmpdir.strpath)
+
+def test_reader_as_cm(tmpdir):
+ j = journal.Reader(path=tmpdir.strpath)
+ with j:
+ assert not j.closed
+ assert j.closed
+ # make sure that operations on the Reader fail
+ with pytest.raises(OSError):
+ next(j)
+
+def test_reader_messageid_match(tmpdir):
+ j = journal.Reader(path=tmpdir.strpath)
+ with j:
+ j.messageid_match(id128.SD_MESSAGE_JOURNAL_START)
+ j.messageid_match(id128.SD_MESSAGE_JOURNAL_STOP.hex)
+
+def test_reader_this_boot(tmpdir):
+ j = journal.Reader(path=tmpdir.strpath)
+ with j:
+ j.this_boot()
+ j.this_boot(TEST_MID)
+ j.this_boot(TEST_MID.hex)
+
+def test_reader_this_machine(tmpdir):
+ j = journal.Reader(path=tmpdir.strpath)
+ with j:
+ j.this_machine()
+ j.this_machine(TEST_MID)
+ j.this_machine(TEST_MID.hex)
+
+def test_reader_converters(tmpdir):
+ converters = {'xxx' : lambda arg: 'yyy'}
+ j = journal.Reader(path=tmpdir.strpath, converters=converters)
+
+ val = j._convert_field('xxx', b'abc')
+ assert val == 'yyy'
+
+ val = j._convert_field('zzz', b'\200\200')
+ assert val == b'\200\200'
+
+def test_reader_convert_entry(tmpdir):
+ converters = {'x1' : lambda arg: 'yyy',
+ 'x2' : lambda arg: 'YYY'}
+ j = journal.Reader(path=tmpdir.strpath, converters=converters)
+
+ val = j._convert_entry({'x1' : b'abc',
+ 'y1' : b'\200\200',
+ 'x2' : [b'abc', b'def'],
+ 'y2' : [b'\200\200', b'\200\201']})
+ assert val == {'x1' : 'yyy',
+ 'y1' : b'\200\200',
+ 'x2' : ['YYY', 'YYY'],
+ 'y2' : [b'\200\200', b'\200\201']}