From a93828172f8f62b0bd2f1363c0932a5853ce7a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 7 Aug 2015 11:47:37 -0400 Subject: tests: add first test This is based on the code in https://github.com/systemd/python-systemd/pull/4 by Jacek Konieczny . --- systemd/test/test_daemon.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 systemd/test/test_daemon.py diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py new file mode 100644 index 0000000..230581d --- /dev/null +++ b/systemd/test/test_daemon.py @@ -0,0 +1,64 @@ +import sys +import os +import posix +from systemd.daemon import _is_fifo, is_fifo + +import pytest + +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) -- cgit v1.2.1 From cc5f218a5082bd8a52f80d1bedad7bd4f78bca5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 5 Sep 2015 12:48:07 +0200 Subject: tests: enable doctests in the sources Unfortunately the "standard" way to access the names in the defined module does not work. I find it nicer to explicitly import, e.g. from systemd import journal, because then the examples correspond more closely to what a user would use. The only exception is made for JournalHandler, because journal.JournalHandler is a tad to long. --- pytest.ini | 2 ++ systemd/journal.py | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..df3eb51 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --doctest-modules diff --git a/systemd/journal.py b/systemd/journal.py index 53bab0e..7105818 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -113,12 +113,15 @@ 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: ... print(entry['MESSAGE']) + starting version ... + ... See systemd.journal-fields(7) for more info on typical fields found in the journal. @@ -361,6 +364,7 @@ def send(MESSAGE, MESSAGE_ID=None, **kwargs): r"""Send a message to the journal. + >>> 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') @@ -415,10 +419,10 @@ def stream(identifier, priority=LOG_DEBUG, level_prefix=False): The file will be line buffered, so messages are actually sent after a newline character is written. + >>> from systemd import journal >>> stream = journal.stream('myapp') - >>> stream - ', mode 'w' at 0x...> >>> stream.write('message...\n') + 11 will produce the following message in the journal:: @@ -451,10 +455,11 @@ class JournalHandler(_logging.Handler): To create a custom logger whose messages go only to journal: + >>> import logging >>> log = logging.getLogger('custom_logger_name') >>> log.propagate = False - >>> log.addHandler(journal.JournalHandler()) - >>> log.warn("Some message: %s", detail) + >>> 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: @@ -464,7 +469,7 @@ class JournalHandler(_logging.Handler): 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 @@ -482,7 +487,8 @@ class JournalHandler(_logging.Handler): 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') + The following journal fields will be sent: `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`, -- cgit v1.2.1 From 26a9c1f18ad2ef63fb3f2676c5ad8f6a565054e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 5 Sep 2015 13:33:40 +0200 Subject: tests: also collect doctests from rst There isn't much to test now, but it doesn't hurt to enable. --- docs/journal.rst | 38 ++++++++++++++++++++++++++++++++++++-- docs/login.rst | 6 +++--- pytest.ini | 2 +- 3 files changed, 40 insertions(+), 6 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': '', + 'CODE_FILE': '', + '_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 index df3eb51..5ca50a5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --doctest-modules +addopts = --doctest-modules --doctest-glob=*.rst -- cgit v1.2.1 From c76c5f0ef76e2de00eb93ee6e35e9d044fe35c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 5 Sep 2015 13:47:47 +0200 Subject: tests: fix test discovery by pytest --- pytest.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 5ca50a5..c5beb19 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,3 @@ [pytest] -addopts = --doctest-modules --doctest-glob=*.rst +addopts = --doctest-modules --doctest-glob=*.rst --ignore=setup.py +norecursedirs = .git build -- cgit v1.2.1 From 2e115f3c4f6f96a2fae0ef011def64ab8b0a11ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 5 Sep 2015 14:13:38 +0200 Subject: tests: daemon.listen_fds --- systemd/test/test_daemon.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index 230581d..96c5717 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -1,7 +1,7 @@ import sys import os import posix -from systemd.daemon import _is_fifo, is_fifo +from systemd.daemon import _is_fifo, is_fifo, listen_fds import pytest @@ -62,3 +62,28 @@ def test_is_fifo_bad_fd(tmpdir): with pytest.raises(OSError): assert not is_fifo(-1, path) + +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() == [] -- cgit v1.2.1 From 0cf0cf7e42b62350fe6c8d6c2cbfb4e9c48a6f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 5 Sep 2015 14:18:32 +0200 Subject: tests: daemon.booted --- systemd/test/test_daemon.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index 96c5717..c048929 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -1,10 +1,18 @@ import sys import os import posix -from systemd.daemon import _is_fifo, is_fifo, listen_fds +from systemd.daemon import _is_fifo, is_fifo, listen_fds, booted import pytest +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) -- cgit v1.2.1 From e6b305b41ab06f8a2eed408aef21baac6faaae67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 5 Sep 2015 14:23:20 +0200 Subject: tests: adapt to python2.7 output again --- systemd/journal.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/systemd/journal.py b/systemd/journal.py index 7105818..2b79b20 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -421,8 +421,7 @@ def stream(identifier, priority=LOG_DEBUG, level_prefix=False): >>> from systemd import journal >>> stream = journal.stream('myapp') - >>> stream.write('message...\n') - 11 + >>> res = stream.write('message...\n') will produce the following message in the journal:: @@ -433,7 +432,7 @@ def stream(identifier, priority=LOG_DEBUG, level_prefix=False): Using the interface with print might be more convinient: >>> from __future__ import print_function - >>> print('message...', file=stream) + >>> print('message...', file=stream) # doctest: +SKIP priority is the syslog priority, one of `LOG_EMERG`, `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`, -- cgit v1.2.1 From 085db21e5e2b0571b7e540f9fde558f5bfef9fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 6 Sep 2015 18:54:55 +0200 Subject: tests: add more tests for socket functions --- systemd/test/test_daemon.py | 86 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index c048929..e412e2e 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -1,10 +1,28 @@ import sys import os import posix -from systemd.daemon import _is_fifo, is_fifo, listen_fds, booted +import socket +import contextlib +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 @@ -71,6 +89,72 @@ def test_is_fifo_bad_fd(tmpdir): with pytest.raises(OSError): assert not is_fifo(-1, path) +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(sock) + assert not is_socket_inet(sock) + + fd = sock.fileno() + assert not is_fifo(fd) + assert not is_mq(fd) + assert not is_socket_inet(fd) + + assert not _is_fifo(fd) + assert not _is_mq(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') -- cgit v1.2.1 From 5f36e8647a981569e5555034383c17ba9b31fefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 8 Sep 2015 10:36:16 +0200 Subject: Normalize some strange indentation --- systemd/journal.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/systemd/journal.py b/systemd/journal.py index 2b79b20..b5c99d8 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -232,9 +232,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() @@ -260,7 +260,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 @@ -335,7 +335,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'). """ @@ -398,8 +399,8 @@ def send(MESSAGE, MESSAGE_ID=None, 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] + 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: -- cgit v1.2.1 From 5bf468dca174f3f275697e8d79a6413de3b56160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 10 Sep 2015 08:44:39 +0200 Subject: tests: start adding tests for JournalHandler --- systemd/test/test_journal.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 systemd/test/test_journal.py diff --git a/systemd/test/test_journal.py b/systemd/test/test_journal.py new file mode 100644 index 0000000..564ead3 --- /dev/null +++ b/systemd/test/test_journal.py @@ -0,0 +1,34 @@ +import logging +from systemd import journal + +import pytest + +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) -- cgit v1.2.1 From 008aac74d7756af5d718e6d35c7a62cab0af584d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 10 Sep 2015 09:00:34 +0200 Subject: journal: reindent to 4 spaces --- systemd/journal.py | 314 ++++++++++++++++++++++++++--------------------------- 1 file changed, 156 insertions(+), 158 deletions(-) diff --git a/systemd/journal.py b/systemd/journal.py index b5c99d8..598a085 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -320,9 +320,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'). """ @@ -353,201 +355,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. - >>> 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') + >>> 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. - >>> from systemd import journal - >>> stream = journal.stream('myapp') - >>> res = 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) # doctest: +SKIP + >>> 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: - >>> import logging - >>> log = logging.getLogger('custom_logger_name') - >>> log.propagate = False - >>> log.addHandler(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(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: - >>> JournalHandler(SYSLOG_IDENTIFIER='my-cool-app') - + >>> JournalHandler(SYSLOG_IDENTIFIER='my-cool-app') + - 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. - 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 + 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 -- cgit v1.2.1 From bbbc6e8a921759e9ea7587356fbc1fe689eb77fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 17 Sep 2015 00:05:36 +0200 Subject: tests: add tests for Reader initialization --- systemd/journal.py | 2 +- systemd/test/test_journal.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/systemd/journal.py b/systemd/journal.py index 598a085..3fe3d83 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -179,7 +179,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): diff --git a/systemd/test/test_journal.py b/systemd/test/test_journal.py index 564ead3..e79a410 100644 --- a/systemd/test/test_journal.py +++ b/systemd/test/test_journal.py @@ -32,3 +32,42 @@ def test_journalhandler_init_exception(): 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_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']} -- cgit v1.2.1 From 4f5aa7b54af5c2393448f38258ff297b43f2b777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 17 Sep 2015 00:10:23 +0200 Subject: journal: allow numbers in field identifiers --- systemd/journal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systemd/journal.py b/systemd/journal.py index 3fe3d83..fd9b4fd 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 -- cgit v1.2.1 From 173c2a89b20a48152eca6136debcf3d43e67d75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 16 Sep 2015 12:43:06 +0200 Subject: tests: add simplistic tests for Reader matches It would be nice to run those tests against fake journal files with the right content to actually test the matches. But those tests are still useful because they test that the interface works as expected. --- systemd/test/test_journal.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/systemd/test/test_journal.py b/systemd/test/test_journal.py index e79a410..ab155da 100644 --- a/systemd/test/test_journal.py +++ b/systemd/test/test_journal.py @@ -1,8 +1,11 @@ import logging -from systemd import journal +import uuid +from systemd import journal, id128 import pytest +TEST_MID = uuid.UUID('8441372f8dca4ca98694a6091fd8519f') + def test_priorities(): p = journal.JournalHandler.mapPriority @@ -48,6 +51,35 @@ def test_reader_init_path(tmpdir): 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) -- cgit v1.2.1 From 4be2fc75bf405ae5e2cac3d8df99e88b46392744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 25 Oct 2015 12:23:29 -0400 Subject: Do not assume specific output from the journal While we *usually* get those messages from udev, in many tests environments this will not be true, so just do not try to check the output at all. --- systemd/journal.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/systemd/journal.py b/systemd/journal.py index fd9b4fd..b319a8a 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -118,10 +118,9 @@ class Reader(_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. -- cgit v1.2.1 From 1d8f5f26dfeeacfd13ec2bb2783a5638d3eb1c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 25 Oct 2015 16:43:58 -0400 Subject: tests: work around bug in sd_is_mq The fix was committed in v226-362-g0260d1d542. --- systemd/test/test_daemon.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index e412e2e..af14f94 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -3,6 +3,7 @@ import os import posix import socket import contextlib +import errno from systemd.daemon import (booted, is_fifo, _is_fifo, is_socket, _is_socket, @@ -89,20 +90,36 @@ def test_is_fifo_bad_fd(tmpdir): 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(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(fd) + assert not is_mq_wrapper(fd) assert not is_socket_inet(fd) assert not _is_fifo(fd) - assert not _is_mq(fd) + assert not _is_mq_wrapper(fd) assert not _is_socket_inet(fd) def test_is_socket(): -- cgit v1.2.1