From 62c3073a815ce4f2c2a64d55c656e04c5c30177b Mon Sep 17 00:00:00 2001 From: wim glenn Date: Sat, 13 Oct 2018 04:26:27 -0500 Subject: [PATCH] add readme info --- MANIFEST.in | 2 + README.rst | 98 ++++++++++++++++++++++++++++++++++++++++++++- pytest_structlog.py | 9 ++--- setup.py | 2 +- tests/test_log.py | 63 ++++++++++++++++++----------- 5 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..065cc87 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +recursive-include tests *.py diff --git a/README.rst b/README.rst index e69635d..c02df88 100644 --- a/README.rst +++ b/README.rst @@ -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 @@ -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 diff --git a/pytest_structlog.py b/pytest_structlog.py index ce57451..46382b1 100644 --- a/pytest_structlog.py +++ b/pytest_structlog.py @@ -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() @@ -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): diff --git a/setup.py b/setup.py index d08f72d..91554d3 100644 --- a/setup.py +++ b/setup.py @@ -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(), diff --git a/tests/test_log.py b/tests/test_log.py index 4f0f018..1af9168 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -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): @@ -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): @@ -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"}, ] @@ -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"}, + ]