[Python-modules-commits] [pytest-catchlog] 01/08: New upstream version 1.2.2+git20170915

Gianfranco Costamagna locutusofborg at moszumanska.debian.org
Fri Sep 15 17:04:58 UTC 2017


This is an automated email from the git hooks/post-receive script.

locutusofborg pushed a commit to branch debian/master
in repository pytest-catchlog.

commit 23e29cf8fdd95222c54f25c3c646f1b5f52efcce
Author: Gianfranco Costamagna <costamagnagianfranco at yahoo.it>
Date:   Fri Sep 15 18:42:01 2017 +0200

    New upstream version 1.2.2+git20170915
---
 .gitignore                            |   1 +
 .travis.yml                           |  28 ++-
 MANIFEST.in                           |   2 +-
 Makefile                              |   4 +-
 README.rst                            |  69 +++++-
 pytest_catchlog.py                    | 313 --------------------------
 pytest_catchlog/__init__.py           |   2 +
 pytest_catchlog/common.py             |  66 ++++++
 pytest_catchlog/fixture.py            | 154 +++++++++++++
 pytest_catchlog/plugin.py             | 264 ++++++++++++++++++++++
 setup.py                              |   9 +-
 tasks.py                              |   2 +-
 test_pytest_catchlog.py               | 300 -------------------------
 tests/conftest.py                     |  33 +++
 tests/perf/__init__.py                |   0
 tests/perf/bench/conftest.py          |  17 ++
 tests/perf/bench/pytest.ini           |  11 +
 tests/perf/bench/test_log.py          |  11 +
 tests/perf/bench/test_runtest_hook.py |  47 ++++
 tests/perf/conftest.py                | 224 +++++++++++++++++++
 tests/perf/data.py                    |  85 ++++++++
 tests/perf/plot.py                    |  85 ++++++++
 tests/perf/test_perf_run.py           |  98 +++++++++
 tests/test_compat.py                  |  80 +++++++
 tests/test_fixture.py                 |  99 +++++++++
 tests/test_reporting.py               | 398 ++++++++++++++++++++++++++++++++++
 tox.ini                               |  14 +-
 27 files changed, 1779 insertions(+), 637 deletions(-)

diff --git a/.gitignore b/.gitignore
index 15fa13f..1f3deae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ __pycache__/
 
 # Unit test / coverage reports
 htmlcov/
+.benchmarks/
 .tox/
 .coverage
 .cache
diff --git a/.travis.yml b/.travis.yml
index 6dcef9e..5663211 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,21 +3,37 @@ language: python
 python:
   - "2.6"
   - "2.7"
-  - "3.2"
   - "3.3"
   - "3.4"
   - "3.5"
   - "pypy"
   - "pypy3"
+matrix:
+  include:
+    - python: "2.7"
+      env: TOXENV=perf
 
-install:
-  - pip install -e .
-script:
-  - py.test
+before_install: |
+  pip install tox virtualenv
+  if [ ! -v TOXENV ]
+  then
+    __py_ver=$TRAVIS_PYTHON_VERSION
+    __tox_dfl=${__py_ver/[0-9].[0-9]/py${__py_ver/.}}
+    export TOXENV=${TOXENV:-$__tox_dfl}
+  fi
+  function announce()
+    { echo -e "$ANSI_GREEN$@${ANSI_RESET}"; }
+  function tox()
+    { announce "Running tox in TOXENV=$TOXENV"; env tox "$@"; }
 
+install: tox --notest
+script: tox
+
+before_cache:
+  - rm -rf $HOME/.cache/pip/log
 cache:
   directories:
-    - $HOME/.cache/pip/http
+    - $HOME/.cache/pip
 
 deploy:
   provider: pypi
diff --git a/MANIFEST.in b/MANIFEST.in
index bc85cc3..d2767c3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
 include MANIFEST.in Makefile LICENSE.txt README.rst CHANGES.rst setup.cfg
-include test_pytest_catchlog.py
+graft tests
 
 global-exclude *pyc
 prune __pycache__
diff --git a/Makefile b/Makefile
index c664c1b..67d64cd 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ clean: clean-pyc clean-coverage
 	-rm -rv build dist *.egg-info
 
 test:
-	py.test -v test_pytest_catchlog.py
+	py.test -v tests
 
 test-coverage:
 	coverage erase
@@ -27,7 +27,7 @@ test-coverage:
 	coverage xml
 
 audit:
-	flake8 pytest_catchlog.py
+	flake8 pytest_catchlog
 
 wheel:
 	python setup.py bdist_wheel
diff --git a/README.rst b/README.rst
index 994decb..469e1a5 100644
--- a/README.rst
+++ b/README.rst
@@ -30,12 +30,12 @@ stderr.
 
 Running without options::
 
-    py.test test_pytest_catchlog.py
+    py.test
 
 Shows failed tests like so::
 
     ----------------------- Captured stdlog call ----------------------
-    test_pytest_catchlog.py    26 INFO     text going to logger
+    test_reporting.py    26 INFO     text going to logger
     ----------------------- Captured stdout call ----------------------
     text going to stdout
     ----------------------- Captured stderr call ----------------------
@@ -50,7 +50,7 @@ format can be specified to anything that the logging module supports.
 Running pytest specifying formatting options::
 
     py.test --log-format="%(asctime)s %(levelname)s %(message)s" \
-            --log-date-format="%Y-%m-%d %H:%M:%S" test_pytest_catchlog.py
+            --log-date-format="%Y-%m-%d %H:%M:%S"
 
 Shows failed tests like so::
 
@@ -77,7 +77,13 @@ error). Command line arguments take precedence.
 Further it is possible to disable reporting logs on failed tests
 completely with::
 
-    py.test --no-print-logs test_pytest_catchlog.py
+    py.test --no-print-logs
+
+Or in you ``pytest.ini``::
+
+  [pytest]
+  log_print=False
+
 
 Shows failed tests in the normal manner as no logs were captured::
 
@@ -88,7 +94,7 @@ Shows failed tests in the normal manner as no logs were captured::
     ==================== 2 failed in 0.02 seconds =====================
 
 Inside tests it is possible to change the log level for the captured
-log messages.  This is supported by the ``caplog`` funcarg::
+log messages.  This is supported by the ``caplog`` fixture::
 
     def test_foo(caplog):
         caplog.set_level(logging.INFO)
@@ -117,7 +123,7 @@ of any logger can be changed instead with::
             pass
 
 Lastly all the logs sent to the logger during the test run are made
-available on the funcarg in the form of both the LogRecord instances
+available on the fixture in the form of both the LogRecord instances
 and the final log text.  This is useful for when you want to assert on
 the contents of a message::
 
@@ -141,3 +147,54 @@ given severity and message::
             ('root', logging.INFO, 'boo arg'),
         ]
 
+You can call ``caplog.clear()`` to reset the captured log records in a test::
+
+    def test_something_with_clearing_records(caplog):
+        some_method_that_creates_log_records()
+        caplog.clear()
+        your_test_method()
+        assert ['Foo'] == [rec.message for rec in caplog.records]
+
+Live Logs
+~~~~~~~~~
+
+By default, catchlog will output any logging records with a level higher or equal
+to WARNING. In order to actually see these logs in the console you have to disable
+pytest output capture by passing ``-s``.
+
+You can specify the logging level for which log records with equal or higher level
+are printed to the console by passing ``--log-cli-level``. This setting accepts the
+logging level names as seen in python's documentation or an integer as the logging
+level num.
+
+Additionally, you can also specify ``--log-cli-format`` and ``--log-cli-date-format``
+which mirror and default to ``--log-format`` and ``--log-date-format`` if not
+provided, but are applied only to the console logging handler.
+
+All of the CLI log options can also be set in the configuration INI file. The option
+names are:
+
+* ``log_cli_level``
+* ``log_cli_format``
+* ``log_cli_date_format``
+
+If you need to record the whole test suite logging calls to a file, you can 
+pass
+``--log-file=/path/to/log/file``. This log file is opened in write mode which means
+that it will be overwritten at each run tests session.
+
+You can also specify the logging level for the log file by passing
+``--log-file-level``. This setting accepts the logging level names as seen in python's
+documentation(ie, uppercased level names) or an integer as the logging level num.
+
+Additionally, you can also specify ``--log-file-format`` and ``--log-file-date-format``
+which are equal to ``--log-format`` and ``--log-date-format`` but are applied to the
+log file logging handler.
+
+All of the log file options can also be set in the configuration INI file. The option
+names are:
+
+* ``log_file``
+* ``log_file_level``
+* ``log_file_format``
+* ``log_file_date_format``
diff --git a/pytest_catchlog.py b/pytest_catchlog.py
deleted file mode 100644
index bd86e8b..0000000
--- a/pytest_catchlog.py
+++ /dev/null
@@ -1,313 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, division, print_function
-
-import functools
-import logging
-from contextlib import closing, contextmanager
-
-import pytest
-import py
-
-
-__version__ = '1.2.2'
-
-
-def get_logger_obj(logger=None):
-    """Get a logger object that can be specified by its name, or passed as is.
-
-    Defaults to the root logger.
-    """
-    if logger is None or isinstance(logger, py.builtin._basestring):
-        logger = logging.getLogger(logger)
-    return logger
-
-
- at contextmanager
-def logging_at_level(level, logger=None):
-    """Context manager that sets the level for capturing of logs."""
-    logger = get_logger_obj(logger)
-
-    orig_level = logger.level
-    logger.setLevel(level)
-    try:
-        yield
-    finally:
-        logger.setLevel(orig_level)
-
-
- at contextmanager
-def logging_using_handler(handler, logger=None):
-    """Context manager that safely register a given handler."""
-    logger = get_logger_obj(logger)
-
-    if handler in logger.handlers:  # reentrancy
-        # Adding the same handler twice would confuse logging system.
-        # Just don't do that.
-        yield
-    else:
-        logger.addHandler(handler)
-        try:
-            yield
-        finally:
-            logger.removeHandler(handler)
-
-
- at contextmanager
-def catching_logs(handler, filter=None, formatter=None,
-                  level=logging.NOTSET, logger=None):
-    """Context manager that prepares the whole logging machinery properly."""
-    logger = get_logger_obj(logger)
-
-    if filter is not None:
-        handler.addFilter(filter)
-    if formatter is not None:
-        handler.setFormatter(formatter)
-    handler.setLevel(level)
-
-    with closing(handler):
-        with logging_using_handler(handler, logger):
-            with logging_at_level(min(handler.level, logger.level), logger):
-
-                yield handler
-
-
-def add_option_ini(parser, option, dest, default=None, help=None):
-    parser.addini(dest, default=default,
-                  help='default value for ' + option)
-    parser.getgroup('catchlog').addoption(option, dest=dest, help=help)
-
-def get_option_ini(config, name):
-    ret = config.getoption(name)  # 'default' arg won't work as expected
-    if ret is None:
-        ret = config.getini(name)
-    return ret
-
-
-def pytest_addoption(parser):
-    """Add options to control log capturing."""
-
-    group = parser.getgroup('catchlog', 'Log catching')
-    group.addoption('--no-print-logs',
-                    dest='log_print', action='store_false', default=True,
-                    help='disable printing caught logs on failed tests.')
-    add_option_ini(parser,
-                   '--log-format',
-                   dest='log_format', default=('%(filename)-25s %(lineno)4d'
-                                               ' %(levelname)-8s %(message)s'),
-                   help='log format as used by the logging module.')
-    add_option_ini(parser,
-                   '--log-date-format',
-                   dest='log_date_format', default=None,
-                   help='log date format as used by the logging module.')
-
-
-def pytest_configure(config):
-    """Always register the log catcher plugin with py.test or tests can't
-    find the  fixture function.
-    """
-    config.pluginmanager.register(CatchLogPlugin(config), '_catch_log')
-
-
-class CatchLogPlugin(object):
-    """Attaches to the logging module and captures log messages for each test.
-    """
-
-    def __init__(self, config):
-        """Creates a new plugin to capture log messages.
-
-        The formatter can be safely shared across all handlers so
-        create a single one for the entire test session here.
-        """
-        self.print_logs = config.getoption('log_print')
-        self.formatter = logging.Formatter(
-                get_option_ini(config, 'log_format'),
-                get_option_ini(config, 'log_date_format'))
-
-    @contextmanager
-    def _runtest_for(self, item, when):
-        """Implements the internals of pytest_runtest_xxx() hook."""
-        with catching_logs(LogCaptureHandler(),
-                           formatter=self.formatter) as log_handler:
-            item.catch_log_handler = log_handler
-            try:
-                yield  # run test
-            finally:
-                del item.catch_log_handler
-
-            if self.print_logs:
-                # Add a captured log section to the report.
-                log = log_handler.stream.getvalue().strip()
-                item.add_report_section(when, 'log', log)
-
-    @pytest.mark.hookwrapper
-    def pytest_runtest_setup(self, item):
-        with self._runtest_for(item, 'setup'):
-            yield
-
-    @pytest.mark.hookwrapper
-    def pytest_runtest_call(self, item):
-        with self._runtest_for(item, 'call'):
-            yield
-
-    @pytest.mark.hookwrapper
-    def pytest_runtest_teardown(self, item):
-        with self._runtest_for(item, 'teardown'):
-            yield
-
-
-class LogCaptureHandler(logging.StreamHandler):
-    """A logging handler that stores log records and the log text."""
-
-    def __init__(self):
-        """Creates a new log handler."""
-
-        logging.StreamHandler.__init__(self)
-        self.stream = py.io.TextIO()
-        self.records = []
-
-    def close(self):
-        """Close this log handler and its underlying stream."""
-
-        logging.StreamHandler.close(self)
-        self.stream.close()
-
-    def emit(self, record):
-        """Keep the log records in a list in addition to the log text."""
-
-        self.records.append(record)
-        logging.StreamHandler.emit(self, record)
-
-
-class LogCaptureFixture(object):
-    """Provides access and control of log capturing."""
-
-    @property
-    def handler(self):
-        return self._item.catch_log_handler
-
-    def __init__(self, item):
-        """Creates a new funcarg."""
-        self._item = item
-
-    @property
-    def text(self):
-        """Returns the log text."""
-        return self.handler.stream.getvalue()
-
-    @property
-    def records(self):
-        """Returns the list of log records."""
-        return self.handler.records
-
-    @property
-    def record_tuples(self):
-        """Returns a list of a striped down version of log records intended
-        for use in assertion comparison.
-
-        The format of the tuple is:
-
-            (logger_name, log_level, message)
-        """
-        return [(r.name, r.levelno, r.getMessage()) for r in self.records]
-
-    def set_level(self, level, logger=None):
-        """Sets the level for capturing of logs.
-
-        By default, the level is set on the handler used to capture
-        logs. Specify a logger name to instead set the level of any
-        logger.
-        """
-
-        obj = logger and logging.getLogger(logger) or self.handler
-        obj.setLevel(level)
-
-    def at_level(self, level, logger=None):
-        """Context manager that sets the level for capturing of logs.
-
-        By default, the level is set on the handler used to capture
-        logs. Specify a logger name to instead set the level of any
-        logger.
-        """
-
-        obj = logger and logging.getLogger(logger) or self.handler
-        return logging_at_level(level, obj)
-
-
-class CallablePropertyMixin(object):
-    """Backward compatibility for functions that became properties."""
-
-    @classmethod
-    def compat_property(cls, func):
-        if isinstance(func, property):
-            make_property = func.getter
-            func = func.fget
-        else:
-            make_property = property
-
-        @functools.wraps(func)
-        def getter(self):
-            naked_value = func(self)
-            ret = cls(naked_value)
-            ret._naked_value = naked_value
-            ret._warn_compat = self._warn_compat
-            ret._prop_name = func.__name__
-            return ret
-
-        return make_property(getter)
-
-    def __call__(self):
-        self._warn_compat(old="'caplog.{0}()' syntax".format(self._prop_name),
-                          new="'caplog.{0}' property".format(self._prop_name))
-        return self._naked_value  # to let legacy clients modify the object
-
-class CallableList(CallablePropertyMixin, list):
-    pass
-
-class CallableStr(CallablePropertyMixin, py.builtin.text):
-    pass
-
-
-class CompatLogCaptureFixture(LogCaptureFixture):
-    """Backward compatibility with pytest-capturelog."""
-
-    def _warn_compat(self, old, new):
-        self._item.warn(code='L1',
-                        message=("{0} is deprecated, use {1} instead"
-                                 .format(old, new)))
-
-    @CallableStr.compat_property
-    def text(self):
-        return super(CompatLogCaptureFixture, self).text
-
-    @CallableList.compat_property
-    def records(self):
-        return super(CompatLogCaptureFixture, self).records
-
-    @CallableList.compat_property
-    def record_tuples(self):
-        return super(CompatLogCaptureFixture, self).record_tuples
-
-    def setLevel(self, level, logger=None):
-        self._warn_compat(old="'caplog.setLevel()'",
-                          new="'caplog.set_level()'")
-        return self.set_level(level, logger)
-
-    def atLevel(self, level, logger=None):
-        self._warn_compat(old="'caplog.atLevel()'",
-                          new="'caplog.at_level()'")
-        return self.at_level(level, logger)
-
-
- at pytest.fixture
-def caplog(request):
-    """Access and control log capturing.
-
-    Captured logs are available through the following methods::
-
-    * caplog.text()          -> string containing formatted log output
-    * caplog.records()       -> list of logging.LogRecord instances
-    * caplog.record_tuples() -> list of (logger_name, level, message) tuples
-    """
-    return CompatLogCaptureFixture(request.node)
-
-capturelog = caplog
diff --git a/pytest_catchlog/__init__.py b/pytest_catchlog/__init__.py
new file mode 100644
index 0000000..495a173
--- /dev/null
+++ b/pytest_catchlog/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+__version__ = '1.2.2'
diff --git a/pytest_catchlog/common.py b/pytest_catchlog/common.py
new file mode 100644
index 0000000..e5fbf23
--- /dev/null
+++ b/pytest_catchlog/common.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+import logging
+from contextlib import closing, contextmanager
+
+import py
+
+
+def get_logger_obj(logger=None):
+    """Get a logger object that can be specified by its name, or passed as is.
+
+    Defaults to the root logger.
+    """
+    if logger is None or isinstance(logger, py.builtin._basestring):
+        logger = logging.getLogger(logger)
+    return logger
+
+
+ at contextmanager
+def logging_at_level(level, logger=None):
+    """Context manager that sets the level for capturing of logs."""
+    logger = get_logger_obj(logger)
+
+    orig_level = logger.level
+    logger.setLevel(level)
+    try:
+        yield
+    finally:
+        logger.setLevel(orig_level)
+
+
+ at contextmanager
+def logging_using_handler(handler, logger=None):
+    """Context manager that safely registers a given handler."""
+    logger = get_logger_obj(logger)
+
+    if handler in logger.handlers:  # reentrancy
+        # Adding the same handler twice would confuse logging system.
+        # Just don't do that.
+        yield
+    else:
+        logger.addHandler(handler)
+        try:
+            yield
+        finally:
+            logger.removeHandler(handler)
+
+
+ at contextmanager
+def catching_logs(handler, filter=None, formatter=None,
+                  level=logging.NOTSET, logger=None):
+    """Context manager that prepares the whole logging machinery properly."""
+    logger = get_logger_obj(logger)
+
+    if filter is not None:
+        handler.addFilter(filter)
+    if formatter is not None:
+        handler.setFormatter(formatter)
+    handler.setLevel(level)
+
+    with closing(handler):
+        with logging_using_handler(handler, logger):
+            with logging_at_level(min(handler.level, logger.level), logger):
+
+                yield handler
diff --git a/pytest_catchlog/fixture.py b/pytest_catchlog/fixture.py
new file mode 100644
index 0000000..ed54bfa
--- /dev/null
+++ b/pytest_catchlog/fixture.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+import functools
+import logging
+
+import pytest
+import py
+
+from pytest_catchlog.common import catching_logs, logging_at_level
+
+
+class LogCaptureFixture(object):
+    """Provides access and control of log capturing."""
+
+    def __init__(self, item):
+        """Creates a new funcarg."""
+        self._item = item
+
+    @property
+    def handler(self):
+        return self._item.catch_log_handler
+
+    @property
+    def text(self):
+        """Returns the log text."""
+        return self.handler.stream.getvalue()
+
+    @property
+    def records(self):
+        """Returns the list of log records."""
+        return self.handler.records
+
+    @property
+    def record_tuples(self):
+        """Returns a list of a striped down version of log records intended
+        for use in assertion comparison.
+
+        The format of the tuple is:
+
+            (logger_name, log_level, message)
+        """
+        return [(r.name, r.levelno, r.getMessage()) for r in self.records]
+
+    def clear(self):
+        """Reset the list of log records."""
+        self.handler.records = []
+
+    def set_level(self, level, logger=None):
+        """Sets the level for capturing of logs.
+
+        By default, the level is set on the handler used to capture
+        logs. Specify a logger name to instead set the level of any
+        logger.
+        """
+
+        obj = logger and logging.getLogger(logger) or self.handler
+        obj.setLevel(level)
+
+    def at_level(self, level, logger=None):
+        """Context manager that sets the level for capturing of logs.
+
+        By default, the level is set on the handler used to capture
+        logs. Specify a logger name to instead set the level of any
+        logger.
+        """
+
+        obj = logger and logging.getLogger(logger) or self.handler
+        return logging_at_level(level, obj)
+
+
+class CallablePropertyMixin(object):
+    """Backward compatibility for functions that became properties."""
+
+    @classmethod
+    def compat_property(cls, func):
+        if isinstance(func, property):
+            make_property = func.getter
+            func = func.fget
+        else:
+            make_property = property
+
+        @functools.wraps(func)
+        def getter(self):
+            naked_value = func(self)
+            ret = cls(naked_value)
+            ret._naked_value = naked_value
+            ret._warn_compat = self._warn_compat
+            ret._prop_name = func.__name__
+            return ret
+
+        return make_property(getter)
+
+    def __call__(self):
+        new = "'caplog.{0}' property".format(self._prop_name)
+        if self._prop_name == 'records':
+            new += ' (or caplog.clear())'
+        self._warn_compat(old="'caplog.{0}()' syntax".format(self._prop_name),
+                          new=new)
+        return self._naked_value  # to let legacy clients modify the object
+
+
+class CallableList(CallablePropertyMixin, list):
+    pass
+
+
+class CallableStr(CallablePropertyMixin, py.builtin.text):
+    pass
+
+
+class CompatLogCaptureFixture(LogCaptureFixture):
+    """Backward compatibility with pytest-capturelog."""
+
+    def _warn_compat(self, old, new):
+        self._item.warn(code='L1',
+                        message=("{0} is deprecated, use {1} instead"
+                                 .format(old, new)))
+
+    @CallableStr.compat_property
+    def text(self):
+        return super(CompatLogCaptureFixture, self).text
+
+    @CallableList.compat_property
+    def records(self):
+        return super(CompatLogCaptureFixture, self).records
+
+    @CallableList.compat_property
+    def record_tuples(self):
+        return super(CompatLogCaptureFixture, self).record_tuples
+
+    def setLevel(self, level, logger=None):
+        self._warn_compat(old="'caplog.setLevel()'",
+                          new="'caplog.set_level()'")
+        return self.set_level(level, logger)
+
+    def atLevel(self, level, logger=None):
+        self._warn_compat(old="'caplog.atLevel()'",
+                          new="'caplog.at_level()'")
+        return self.at_level(level, logger)
+
+
+ at pytest.fixture
+def caplog(request):
+    """Access and control log capturing.
+
+    Captured logs are available through the following methods::
+
+    * caplog.text()          -> string containing formatted log output
+    * caplog.records()       -> list of logging.LogRecord instances
+    * caplog.record_tuples() -> list of (logger_name, level, message) tuples
+    """
+    return CompatLogCaptureFixture(request.node)
+
+capturelog = caplog
diff --git a/pytest_catchlog/plugin.py b/pytest_catchlog/plugin.py
new file mode 100644
index 0000000..fe1bd1d
--- /dev/null
+++ b/pytest_catchlog/plugin.py
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+import logging
+import sys
+from contextlib import closing, contextmanager
+
+import pytest
+import py
+
+from pytest_catchlog.common import catching_logs
+
+# Let the fixtures be discoverable by pytest.
+from pytest_catchlog.fixture import caplog, capturelog
+
+
+DEFAULT_LOG_FORMAT = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'
+DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'
+
+
+def add_option_ini(parser, option, dest, default=None, **kwargs):
+    parser.addini(dest, default=default,
+                  help='default value for ' + option)
+    parser.getgroup('catchlog').addoption(option, dest=dest, **kwargs)
+
+
+def get_option_ini(config, name):
+    ret = config.getoption(name)  # 'default' arg won't work as expected
+    if ret is None:
+        ret = config.getini(name)
+    return ret
+
+
+def pytest_addoption(parser):
+    """Add options to control log capturing."""
+
+    group = parser.getgroup('catchlog', 'Log catching')
+    add_option_ini(parser,
+        '--no-print-logs',
+        dest='log_print', action='store_const', const=False, default=True,
+        help='disable printing caught logs on failed tests.'
+    )
+    add_option_ini(
+        parser,
+        '--log-level',
+        dest='log_level', default=None,
+        help='logging level used by the logging module'
+    )
+    add_option_ini(parser,
+        '--log-format',
+        dest='log_format', default=DEFAULT_LOG_FORMAT,
+        help='log format as used by the logging module.'
+    )
+    add_option_ini(parser,
+        '--log-date-format',
+        dest='log_date_format', default=DEFAULT_LOG_DATE_FORMAT,
+        help='log date format as used by the logging module.'
+    )
+    add_option_ini(
+        parser,
+        '--log-cli-level',
+        dest='log_cli_level', default=None,
+        help='cli logging level.'
+    )
+    add_option_ini(
+        parser,
+        '--log-cli-format',
+        dest='log_cli_format', default=None,
+        help='log format as used by the logging module.'
+    )
+    add_option_ini(
+        parser,
+        '--log-cli-date-format',
+        dest='log_cli_date_format', default=None,
+        help='log date format as used by the logging module.'
+    )
+    add_option_ini(
+        parser,
+        '--log-file',
+        dest='log_file', default=None,
+        help='path to a file when logging will be written to.'
+    )
+    add_option_ini(
+        parser,
+        '--log-file-level',
+        dest='log_file_level', default=None,
+        help='log file logging level.'
+    )
+    add_option_ini(
+        parser,
+        '--log-file-format',
+        dest='log_file_format', default=DEFAULT_LOG_FORMAT,
+        help='log format as used by the logging module.'
+    )
+    add_option_ini(
+        parser,
+        '--log-file-date-format',
+        dest='log_file_date_format', default=DEFAULT_LOG_DATE_FORMAT,
+        help='log date format as used by the logging module.'
+    )
+
+
+
+def get_actual_log_level(config, setting_name):
+    """Return the actual logging level."""
+    log_level = get_option_ini(config, setting_name)
+    if not log_level:
+        return
+    if isinstance(log_level, py.builtin.text):
+        log_level = log_level.upper()
+    try:
+        return int(getattr(logging, log_level, log_level))
+    except ValueError:
+        # Python logging does not recognise this as a logging level
+        raise pytest.UsageError(
+            "'{0}' is not recognized as a logging level name for "
+            "'{1}'. Please consider passing the "
+            "logging level num instead.".format(
+                log_level,
+                setting_name))
+
+
+def pytest_configure(config):
+    """Always register the log catcher plugin with py.test or tests can't
+    find the  fixture function.
+    """
+    log_cli_level = get_actual_log_level(config, 'log_cli_level')
+    if log_cli_level is None:
+        # No specific CLI logging level was provided, let's check
+        # log_level for a fallback
+        log_cli_level = get_actual_log_level(config, 'log_level')
+        if log_cli_level is None:
+            # No log_level was provided, default to WARNING
+            log_cli_level = logging.WARNING
+    config._catchlog_log_cli_level = log_cli_level
+    config._catchlog_log_file = get_option_ini(config, 'log_file')
+    if config._catchlog_log_file:
+        log_file_level = get_actual_log_level(config, 'log_file_level')
+        if log_file_level is None:
+            # No log_level was provided, default to WARNING
+            log_file_level = logging.WARNING
+        config._catchlog_log_file_level = log_file_level
+    config.pluginmanager.register(CatchLogPlugin(config), '_catch_log')
+
+
+class CatchLogPlugin(object):
+    """Attaches to the logging module and captures log messages for each test.
+    """
+
+    def __init__(self, config):
+        """Creates a new plugin to capture log messages.
+
+        The formatter can be safely shared across all handlers so
+        create a single one for the entire test session here.
+        """
+        print_logs = get_option_ini(config, 'log_print')
+        if not isinstance(print_logs, bool):
+            if print_logs.lower() in ('true', 'yes', '1'):
+                print_logs = True
+            elif print_logs.lower() in ('false', 'no', '0'):
+                print_logs = False
+        self.print_logs = print_logs
+        self.formatter = logging.Formatter(
+                get_option_ini(config, 'log_format'),
+                get_option_ini(config, 'log_date_format'))
+        self.log_cli_handler = logging.StreamHandler(sys.stderr)
+        log_cli_format = get_option_ini(config, 'log_cli_format')
+        if not log_cli_format:
+            # No CLI specific format was provided, use log_format
+            log_cli_format = get_option_ini(config, 'log_format')
+        log_cli_date_format = get_option_ini(config, 'log_cli_date_format')
+        if not log_cli_date_format:
+            # No CLI specific date format was provided, use log_date_format
+            log_cli_date_format = get_option_ini(config, 'log_date_format')
+        log_cli_formatter = logging.Formatter(
+                log_cli_format,
+                datefmt=log_cli_date_format)
+        self.log_cli_handler.setFormatter(log_cli_formatter)
+        if config._catchlog_log_file:
+            log_file_format = get_option_ini(config, 'log_file_format')
+            if not log_file_format:
+                # No log file specific format was provided, use log_format
+                log_file_format = get_option_ini(config, 'log_format')
+            log_file_date_format = get_option_ini(config, 'log_file_date_format')
+            if not log_file_date_format:
+                # No log file specific date format was provided, use log_date_format
+                log_file_date_format = get_option_ini(config, 'log_date_format')
+            self.log_file_handler = logging.FileHandler(
+                config._catchlog_log_file,
+                # Each pytest runtests session will write to a clean logfile
+                mode='w',
... 1722 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/pytest-catchlog.git



More information about the Python-modules-commits mailing list