Skip to content

Commit

Permalink
Merge pull request #2 from wimglenn/docs
Browse files Browse the repository at this point in the history
add readme info
  • Loading branch information
wimglenn authored Oct 13, 2018
2 parents 03826a6 + 62c3073 commit f38ee88
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 32 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include LICENSE
recursive-include tests *.py
98 changes: 96 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
|travis|_ |pypi|_ |pyversions|_
|travis|_ |pypi|_ |pyversions|_ |womm|_

.. |travis| image:: https://img.shields.io/travis/wimglenn/pytest-structlog.svg?branch=master
.. _travis: https://travis-ci.org/wimglenn/pytest-structlog
Expand All @@ -9,8 +9,102 @@
.. |pyversions| image:: https://img.shields.io/pypi/pyversions/pytest-structlog.svg
.. _pyversions:

.. |womm| image:: https://cdn.rawgit.com/nikku/works-on-my-machine/v0.2.0/badge.svg
.. _womm: https://github.com/nikku/works-on-my-machine


pytest-structlog
================

Structured logging assertions
Structured logging assertions. pytest_ + structlog_ = ``pytest-structlog``.

|pytest| |structlog|


Installation:
-------------

.. code-block:: bash
$ pip install pytest-structlog
Usage:
------

The fixture name is ``log``. It has two attributes of interest: ``log.events`` is a list of events from captured log calls, and ``log.has`` is a helper function for asserting a single event was logged within the expected context.

Suppose you have some library module, ``your_lib``, which is using ``structlog``:

.. code-block:: python
# your_lib.py
from structlog import get_logger
logger = get_logger(__name__)
def spline_reticulator():
logger.info("reticulating splines")
for i in range(3):
logger.debug("processing", spline=i)
logger.info("reticulated splines", n_splines=3)
Then your test suite might use assertions such as shown below:

.. code-block:: python
# test_your_lib.py
from your_lib import spline_reticulator
def test_spline_reticulator(log):
assert len(log.events) == 0
spline_reticulator()
assert len(log.events) == 5
# can assert on the event only
assert log.has("reticulating splines")
# can assert with subcontext
assert log.has("reticulated splines")
assert log.has("reticulated splines", n_splines=3)
assert log.has("reticulated splines", n_splines=3, level="info")
# but not incorrect context
assert not log.has("reticulated splines", n_splines=42)
assert not log.has("reticulated splines", key="bogus")
# can assert with the event dicts directly
assert log.events == [
{"event": "reticulating splines", "level": "info"},
{"event": "processing", "level": "debug", "spline": 0},
{"event": "processing", "level": "debug", "spline": 1},
{"event": "processing", "level": "debug", "spline": 2},
{"event": "reticulated splines", "level": "info", "n_splines": 3},
]
# can use membership to check for a single event's data
assert {"event": "reticulating splines", "level": "info"} in log.events
# can use >= to specify only the events you're interested in
assert log.events >= [
{"event": "processing", "level": "debug", "spline": 0},
{"event": "processing", "level": "debug", "spline": 2},
]
# or put the comparison the other way around if you prefer
assert [
{"event": "processing", "level": "debug", "spline": 0},
{"event": "processing", "level": "debug", "spline": 2},
] <= log.events
# note: comparisons are order sensitive!
assert not [
{"event": "processing", "level": "debug", "spline": 2},
{"event": "processing", "level": "debug", "spline": 0},
] <= log.events
.. _pytest: https://docs.pytest.org/
.. _structlog: https://www.structlog.org/
.. |pytest| image:: https://user-images.githubusercontent.com/6615374/46903931-515eef00-cea2-11e8-8945-980ddbf0a053.png
.. |structlog| image:: https://user-images.githubusercontent.com/6615374/46903937-5b80ed80-cea2-11e8-9b85-d3f071180fe1.png
9 changes: 3 additions & 6 deletions pytest_structlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ def __le__(self, other):
def __lt__(self, other):
return len(self) < len(other) and is_subseq(self, other)

def has(self, message, **context):
context["event"] = message
return any(is_submap(context, e) for e in self)


absent = object()

Expand All @@ -50,8 +46,9 @@ def process(self, logger, method_name, event_dict):
self.events.append(event_dict)
raise structlog.DropEvent

def had(self, message, **context):
return self.events.has(message, **context)
def has(self, message, **context):
context["event"] = message
return any(is_submap(context, e) for e in self.events)


def no_op(*args, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="pytest-structlog",
version="0.1a0",
version="0.1",
url="https://github.com/wimglenn/pytest-structlog",
description="Structured logging assertions",
long_description=open("README.rst").read(),
Expand Down
63 changes: 40 additions & 23 deletions tests/test_log.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import structlog


logger = structlog.get_logger('test')


# pytest_plugins = ["pytester"]
logger = structlog.get_logger("test")


def spline_reticulator():
logger.info('reticulating splines', n_splines=123)
logger.info("reticulating splines", n_splines=123)


def main_ish():
structlog.configure(processors=[]) # this should be no-op when the fixture is injected
logger.debug('yo')
structlog.configure(processors=[])
# this configure call should be a no-op after fixture was injected
logger.debug("yo")


def binding():
log = logger.bind(k='v')
log.debug('dbg')
log.info('inf', kk='more context')
log = log.unbind('k')
log.warning('uh-oh')
log = logger.bind(k="v")
log.debug("dbg")
log.info("inf", kk="more context")
log = log.unbind("k")
log.warning("uh-oh")


def test_capture_creates_items(log):
Expand All @@ -32,38 +30,38 @@ def test_capture_creates_items(log):

def test_assert_without_context(log):
spline_reticulator()
assert log.had('reticulating splines')
assert log.has("reticulating splines")


def test_assert_with_subcontext(log):
spline_reticulator()
assert log.had('reticulating splines', n_splines=123)
assert log.has("reticulating splines", n_splines=123)


def test_assert_with_bogus_context(log):
spline_reticulator()
assert not log.had('reticulating splines', n_splines=0)
assert not log.has("reticulating splines", n_splines=0)


def test_assert_with_all_context(log):
spline_reticulator()
assert log.had('reticulating splines', n_splines=123, level='info')
assert log.has("reticulating splines", n_splines=123, level="info")


def test_assert_with_super_context(log):
spline_reticulator()
assert not log.had('reticulating splines', n_splines=123, level='info', k='v')
assert not log.has("reticulating splines", n_splines=123, level="info", k="v")


def test_configurator(log):
main_ish()
assert log.had('yo', level='debug')
assert log.has("yo", level="debug")


def test_multiple_events(log):
binding()
assert log.had('dbg', k='v', level='debug')
assert log.had('inf', k='v', kk='more context', level='info')
assert log.has("dbg", k="v", level="debug")
assert log.has("inf", k="v", kk="more context", level="info")


def test_length(log):
Expand All @@ -72,9 +70,9 @@ def test_length(log):


d0, d1, d2 = [
{'event': 'dbg', 'k': 'v', 'level': 'debug'},
{'event': 'inf', 'k': 'v', 'level': 'info', 'kk': 'more context'},
{'event': 'uh-oh', 'level': 'warning'},
{"event": "dbg", "k": "v", "level": "debug"},
{"event": "inf", "k": "v", "level": "info", "kk": "more context"},
{"event": "uh-oh", "level": "warning"},
]


Expand Down Expand Up @@ -123,3 +121,22 @@ def test_total_ordering(log):
assert log.events <= [d0, d1, d2, {}]
assert log.events < [d0, d1, d2, {}]
assert log.events > [d0, d1]


def test_dupes(log):
logger.x("a")
logger.x("a")
logger.x("b")
assert log.events >= [{"event": "a", "level": "x"}]
assert log.events >= [{"event": "a", "level": "x"}, {"event": "b", "level": "x"}]
assert log.events >= [
{"event": "a", "level": "x"},
{"event": "a", "level": "x"},
{"event": "b", "level": "x"},
]
assert not log.events >= [
{"event": "a", "level": "x"},
{"event": "a", "level": "x"},
{"event": "a", "level": "x"},
{"event": "b", "level": "x"},
]

0 comments on commit f38ee88

Please sign in to comment.