[Python-modules-commits] [pytest-catchlog] 01/07: Import pytest-catchlog_1.2.1.orig.tar.xz

daniel at danielstender.com daniel at danielstender.com
Wed Dec 30 18:47:07 UTC 2015


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

danstender-guest pushed a commit to branch master
in repository pytest-catchlog.

commit 404dfc09250a5bb3e820347ff1ecdfb9e9f7fb85
Author: Daniel Stender <debian at danielstender.com>
Date:   Wed Dec 30 19:25:18 2015 +0100

    Import pytest-catchlog_1.2.1.orig.tar.xz
---
 .travis.yml             |  20 ++++
 CHANGES.rst             |  48 +++++++-
 MANIFEST.in             |   1 +
 README.rst              |  42 ++++---
 pytest_catchlog.py      | 287 +++++++++++++++++++++++++++++++++---------------
 setup.py                |   9 +-
 tasks.py                | 188 +++++++++++++++++++++++++++++++
 test_pytest_catchlog.py | 156 +++++++++++++++++++++-----
 tox.ini                 |   2 +-
 9 files changed, 615 insertions(+), 138 deletions(-)

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..db995e2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,20 @@
+sudo: false
+language: python
+python:
+  - "2.6"
+  - "2.7"
+  - "3.2"
+  - "3.3"
+  - "3.4"
+  - "3.5"
+  - "pypy"
+  - "pypy3"
+
+install:
+  - pip install -e .
+script:
+  - py.test
+
+cache:
+  directories:
+    - $HOME/.cache/pip/http
diff --git a/CHANGES.rst b/CHANGES.rst
index b7d938e..ed164ab 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -3,6 +3,53 @@ Changelog
 
 List of notable changes between pytest-catchlog releases.
 
+.. %UNRELEASED_SECTION%
+
+`Unreleased`_
+-------------
+
+Yet to be released.
+
+
+`1.2.1`_
+-------------
+
+Released on 2015-12-07.
+
+- [Bugfix] #18 - Allow ``caplog.records()`` to be modified.  Thanks to Eldar Abusalimov for the PR and Marco Nenciarini for reporting the issue.
+- [Bugfix] #15 #17 - Restore Python 2.6 compatibility. (Thanks to Marco Nenciarini!)
+
+.. attention::
+    Deprecation warning: the following objects (i.e. functions, properties)
+    are slated for removal in the next major release.
+
+    - ``caplog.at_level`` and ``caplog.set_level`` should be used instead of
+      ``caplog.atLevel`` and ``caplog.setLevel``.
+
+      The methods ``caplog.atLevel`` and ``caplog.setLevel`` are still
+      available but deprecated and not supported since they don't follow
+      the PEP8 convention for method names.
+
+    - ``caplog.text``, ``caplog.records`` and
+      ``caplog.record_tuples`` were turned into properties.
+      They still can be used as regular methods for backward compatibility,
+      but that syntax is considered deprecated and scheduled for removal in
+      the next major release.
+
+
+Version 1.2
+-----------
+
+Released on 2015-11-08.
+
+- [Feature] #6 - Configure logging message and date format through ini file.
+- [Feature] #7 - Also catch logs from setup and teardown stages.
+- [Feature] #7 - Replace deprecated ``__multicall__`` use to support future Py.test releases.
+- [Feature] #11 - reintroduce ``setLevel`` and ``atLevel`` to retain backward compatibility with pytest-capturelog.  Also the members ``text``, ``records`` and ``record_tuples`` of the ``caplog`` fixture can be used as properties now.
+
+Special thanks for this release goes to Eldar Abusalimov.  He provided all of the changed features.
+
+
 Version 1.1
 -----------
 
@@ -20,4 +67,3 @@ Released on 2014-12-08.
 
 - Add ``record_tuples`` for comparing recorded log entries against expected
   log entries with their logger name, severity and formatted message.
-
diff --git a/MANIFEST.in b/MANIFEST.in
index 8ad1455..bc85cc3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,5 @@
 include MANIFEST.in Makefile LICENSE.txt README.rst CHANGES.rst setup.cfg
+include test_pytest_catchlog.py
 
 global-exclude *pyc
 prune __pycache__
diff --git a/README.rst b/README.rst
index 2287412..bc7fcba 100644
--- a/README.rst
+++ b/README.rst
@@ -30,11 +30,11 @@ Running without options::
 
 Shows failed tests like so::
 
-    -------------------------- Captured log ---------------------------
+    ----------------------- Captured stdlog call ----------------------
     test_pytest_catchlog.py    26 INFO     text going to logger
-    ------------------------- Captured stdout -------------------------
+    ----------------------- Captured stdout call ----------------------
     text going to stdout
-    ------------------------- Captured stderr -------------------------
+    ----------------------- Captured stderr call ----------------------
     text going to stderr
     ==================== 2 failed in 0.02 seconds =====================
 
@@ -50,14 +50,26 @@ Running pytest specifying formatting options::
 
 Shows failed tests like so::
 
-    -------------------------- Captured log ---------------------------
+    ----------------------- Captured stdlog call ----------------------
     2010-04-10 14:48:44 INFO text going to logger
-    ------------------------- Captured stdout -------------------------
+    ----------------------- Captured stdout call ----------------------
     text going to stdout
-    ------------------------- Captured stderr -------------------------
+    ----------------------- Captured stderr call ----------------------
     text going to stderr
     ==================== 2 failed in 0.02 seconds =====================
 
+These options can also be customized through a configuration file::
+
+    [pytest]
+    log_format = %(asctime)s %(levelname)s %(message)s
+    log_date_format = %Y-%m-%d %H:%M:%S
+
+Although the same effect could be achieved through the ``addopts`` setting,
+using dedicated options should be preferred since the latter doesn't
+force other developers to have ``pytest-catchlog`` installed (while at
+the same time, ``addopts`` approach would fail with 'unrecognized arguments'
+error). Command line arguments take precedence.
+
 Further it is possible to disable reporting logs on failed tests
 completely with::
 
@@ -65,9 +77,9 @@ completely with::
 
 Shows failed tests in the normal manner as no logs were captured::
 
-    ------------------------- Captured stdout -------------------------
+    ----------------------- Captured stdout call ----------------------
     text going to stdout
-    ------------------------- Captured stderr -------------------------
+    ----------------------- Captured stderr call ----------------------
     text going to stderr
     ==================== 2 failed in 0.02 seconds =====================
 
@@ -75,7 +87,7 @@ Inside tests it is possible to change the log level for the captured
 log messages.  This is supported by the ``caplog`` funcarg::
 
     def test_foo(caplog):
-        caplog.setLevel(logging.INFO)
+        caplog.set_level(logging.INFO)
         pass
 
 By default the level is set on the handler used to catch the log
@@ -83,21 +95,21 @@ messages, however as a convenience it is also possible to set the log
 level of any logger::
 
     def test_foo(caplog):
-        caplog.setLevel(logging.CRITICAL, logger='root.baz')
+        caplog.set_level(logging.CRITICAL, logger='root.baz')
         pass
 
 It is also possible to use a context manager to temporarily change the
 log level::
 
     def test_bar(caplog):
-        with caplog.atLevel(logging.INFO):
+        with caplog.at_level(logging.INFO):
             pass
 
 Again, by default the level of the handler is affected but the level
 of any logger can be changed instead with::
 
     def test_bar(caplog):
-        with caplog.atLevel(logging.CRITICAL, logger='root.baz'):
+        with caplog.at_level(logging.CRITICAL, logger='root.baz'):
             pass
 
 Lastly all the logs sent to the logger during the test run are made
@@ -107,9 +119,9 @@ the contents of a message::
 
     def test_baz(caplog):
         func_under_test()
-        for record in caplog.records():
+        for record in caplog.records:
             assert record.levelname != 'CRITICAL'
-        assert 'wally' not in caplog.text()
+        assert 'wally' not in caplog.text
 
 For all the available attributes of the log records see the
 ``logging.LogRecord`` class.
@@ -121,7 +133,7 @@ given severity and message::
     def test_foo(caplog):
         logging.getLogger().info('boo %s', 'arg')
 
-        assert caplog.record_tuples() == [
+        assert caplog.record_tuples == [
             ('root', logging.INFO, 'boo arg'),
         ]
 
diff --git a/pytest_catchlog.py b/pytest_catchlog.py
index 61cdb94..892fd68 100644
--- a/pytest_catchlog.py
+++ b/pytest_catchlog.py
@@ -1,30 +1,104 @@
 # -*- coding: utf-8 -*-
-from __future__ import (absolute_import, print_function,
-                        unicode_literals, division)
+from __future__ import absolute_import, division, print_function
 
+import functools
 import logging
+from contextlib import closing, contextmanager
 
+import pytest
 import py
 
 
-__version__ = '1.1'
+__version__ = '1.2.1'
+
+
+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 = 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.')
-    group.addoption('--log-format',
-                    dest='log_format',
-                    default=('%(filename)-25s %(lineno)4d'
-                             ' %(levelname)-8s %(message)s'),
-                    help='log format as used by the logging module.')
-    group.addoption('--log-date-format',
-                    dest='log_date_format', default=None,
-                    help='log date format as used by the logging module.')
+    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):
@@ -44,62 +118,44 @@ class CatchLogPlugin(object):
         The formatter can be safely shared across all handlers so
         create a single one for the entire test session here.
         """
-        self.print_logs = config.getvalue('log_print')
-        self.formatter = logging.Formatter(config.getvalue('log_format'),
-                                           config.getvalue('log_date_format'))
-
+        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):
-        """Start capturing log messages for this test.
-
-        Creating a specific handler for each test ensures that we
-        avoid multi threading issues.
-
-        Attaching the handler and setting the level at the beginning
-        of each test ensures that we are setup to capture log
-        messages.
-        """
+        with self._runtest_for(item, 'setup'):
+            yield
 
-        # Create a handler for this test.
-        item.catch_log_handler = CatchLogHandler()
-        item.catch_log_handler.setFormatter(self.formatter)
+    @pytest.mark.hookwrapper
+    def pytest_runtest_call(self, item):
+        with self._runtest_for(item, 'call'):
+            yield
 
-        # Attach the handler to the root logger and ensure that the
-        # root logger is set to log all levels.
-        root_logger = logging.getLogger()
-        root_logger.addHandler(item.catch_log_handler)
-        root_logger.setLevel(logging.NOTSET)
+    @pytest.mark.hookwrapper
+    def pytest_runtest_teardown(self, item):
+        with self._runtest_for(item, 'teardown'):
+            yield
 
-    def pytest_runtest_makereport(self, __multicall__, item, call):
-        """Add captured log messages for this report."""
 
-        report = __multicall__.execute()
-
-        # This fn called after setup, call and teardown.  Only
-        # interested in just after test call has finished.
-        if call.when == 'call':
-
-            # Detach the handler from the root logger to ensure no
-            # further access to the handler.
-            root_logger = logging.getLogger()
-            root_logger.removeHandler(item.catch_log_handler)
-
-            # For failed tests that have captured log messages add a
-            # captured log section to the report if desired.
-            if not report.passed and self.print_logs:
-                long_repr = getattr(report, 'longrepr', None)
-                if hasattr(long_repr, 'addsection'):
-                    log = item.catch_log_handler.stream.getvalue().strip()
-                    if log:
-                        long_repr.addsection('Captured log', log)
-
-            # Release the handler resources.
-            item.catch_log_handler.close()
-            del item.catch_log_handler
-
-        return report
-
-
-class CatchLogHandler(logging.StreamHandler):
+class LogCaptureHandler(logging.StreamHandler):
     """A logging handler that stores log records and the log text."""
 
     def __init__(self):
@@ -122,24 +178,28 @@ class CatchLogHandler(logging.StreamHandler):
         logging.StreamHandler.emit(self, record)
 
 
-class CatchLogFuncArg(object):
+class LogCaptureFixture(object):
     """Provides access and control of log capturing."""
 
-    def __init__(self, handler):
-        """Creates a new funcarg."""
+    @property
+    def handler(self):
+        return self._item.catch_log_handler
 
-        self.handler = 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.
@@ -148,7 +208,7 @@ class CatchLogFuncArg(object):
 
             (logger_name, log_level, message)
         """
-        return [(r.name, r.levelno, r.getMessage()) for r in self.records()]
+        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.
@@ -170,37 +230,84 @@ class CatchLogFuncArg(object):
         """
 
         obj = logger and logging.getLogger(logger) or self.handler
-        return CatchLogLevel(obj, level)
+        return logging_at_level(level, obj)
 
 
-class CatchLogLevel(object):
-    """Context manager that sets the logging level of a handler or logger."""
+class CallablePropertyMixin(object):
+    """Backward compatibility for functions that became properties."""
 
-    def __init__(self, obj, level):
-        """Creates a new log level context manager."""
+    @classmethod
+    def compat_property(cls, func):
+        if isinstance(func, property):
+            make_property = func.getter
+            func = func.fget
+        else:
+            make_property = property
 
-        self.obj = obj
-        self.level = level
+        @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
 
-    def __enter__(self):
-        """Adjust the log level."""
+        return make_property(getter)
 
-        self.orig_level = self.obj.level
-        self.obj.setLevel(self.level)
+    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
 
-    def __exit__(self, exc_type, exc_value, traceback):
-        """Restore the log level."""
+class CallableList(CallablePropertyMixin, list):
+    pass
 
-        self.obj.setLevel(self.orig_level)
+class CallableStr(CallablePropertyMixin, str):
+    pass
 
 
-def pytest_funcarg__caplog(request):
-    """Returns a funcarg to access and control log capturing."""
+class CompatLogCaptureFixture(LogCaptureFixture):
+    """Backward compatibility with pytest-capturelog."""
 
-    return CatchLogFuncArg(request._pyfuncitem.catch_log_handler)
+    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 pytest_funcarg__capturelog(request):
-    """Returns a funcarg to access and control log capturing."""
+    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)
 
-    return CatchLogFuncArg(request._pyfuncitem.catch_log_handler)
+capturelog = caplog
diff --git a/setup.py b/setup.py
index ba84092..0a99448 100755
--- a/setup.py
+++ b/setup.py
@@ -21,26 +21,29 @@ setup(name='pytest-catchlog',
       version=_get_version(),
       description=('py.test plugin to catch log messages.'
                    ' This is a fork of pytest-capturelog.'),
-      long_description=_read_text_file('README.rst'),
+      long_description='\n'.join([_read_text_file('README.rst'),
+                                  _read_text_file('CHANGES.rst'), ]),
       author='Arthur Skowronek (Fork Author)',  # original author: Meme Dough
       author_email='eisensheng at mailbox.org',
       url='https://github.com/eisensheng/pytest-catchlog',
       py_modules=['pytest_catchlog', ],
-      install_requires=['py>=1.1.1', ],
+      install_requires=['py>=1.1.1', 'pytest>=2.6'],
       entry_points={'pytest11': ['pytest_catchlog = pytest_catchlog']},
       license='MIT License',
       zip_safe=False,
-      keywords='py.test pytest',
+      keywords='py.test pytest logging',
       classifiers=['Development Status :: 4 - Beta',
                    'Intended Audience :: Developers',
                    'License :: OSI Approved :: MIT License',
                    'Operating System :: OS Independent',
                    'Programming Language :: Python',
+                   'Programming Language :: Python :: 2.6',
                    'Programming Language :: Python :: 2.7',
                    'Programming Language :: Python :: 3',
                    'Programming Language :: Python :: 3.2',
                    'Programming Language :: Python :: 3.3',
                    'Programming Language :: Python :: 3.4',
+                   'Programming Language :: Python :: 3.5',
                    'Programming Language :: Python :: Implementation :: CPython',
                    'Programming Language :: Python :: Implementation :: PyPy',
                    'Topic :: Software Development :: Testing'])
diff --git a/tasks.py b/tasks.py
new file mode 100644
index 0000000..e271c99
--- /dev/null
+++ b/tasks.py
@@ -0,0 +1,188 @@
+# -*- coding: utf-8 -*-
+import os
+import re
+import io
+from contextlib import contextmanager
+from datetime import datetime
+
+from invoke import task, run
+
+VERSION_FILE = 'pytest_catchlog.py'
+CHANGE_LOG_FILE = 'CHANGES.rst'
+
+
+def _path_abs_join(*nodes):
+    return os.path.abspath(os.path.join(os.path.dirname(__file__), *nodes))
+
+
+def _path_open(*nodes, **kwargs):
+    return io.open(_path_abs_join(*nodes), **kwargs)
+
+
+def _shell_quote(s):
+    """Quote given string to be suitable as input for bash as argument."""
+    if not s:
+        return "''"
+    if re.search(r'[^\w@%+=:,./-]', s) is None:
+        return s
+    return "'" + s.replace("'", "'\"'\"'") + "'"
+
+
+def _git_do(*commands, **kwargs):
+    """Execute arbitrary git commands."""
+    kwargs.setdefault('hide', 'out')
+    results = [run('git ' + command, **kwargs).stdout.strip('\n')
+               for command in commands]
+    return results if len(commands) > 1 else results[0]
+
+
+def _git_checkout(branch_name):
+    """Switches to the given branch name."""
+    return _git_do('checkout ' + _shell_quote(branch_name))
+
+
+ at contextmanager
+def _git_work_on(branch_name):
+    """Work on given branch. Preserves current git branch."""
+    original_branch = _git_do('rev-parse --abbrev-ref HEAD')
+    try:
+        if original_branch != branch_name:
+            _git_checkout(branch_name)
+        yield
+    finally:
+        if original_branch and original_branch != branch_name:
+            _git_checkout(original_branch)
+
+
+def _version_find_existing():
+    """Returns set of existing versions in this repository.
+
+    This information is backed by previously used version tags
+    stored in the git repository.
+    """
+    git_tags = [y.strip() for y in _git_do('tag -l').split('\n')]
+
+    _version_re = re.compile(r'^v?(\d+)(?:\.(\d+)(?:\.(\d+))?)?$')
+    return {tuple(int(n) if n else 0 for n in m.groups())
+            for m in (_version_re.match(t) for t in git_tags if t) if m}
+
+
+def _version_find_latest():
+    """Returns the most recent used version number.
+
+    This information is backed by previously used version tags
+    stored in the git repository.
+    """
+    return max(_version_find_existing())
+
+
+def _version_guess_next(position='minor'):
+    """Guess next version.
+
+    A guess for the next version is determined by incrementing given
+    position or minor level position in latest existing version.
+    """
+    try:
+        latest_version = list(_version_find_latest())
+    except ValueError:
+        latest_version = [0, 0, 0]
+
+    position_index = {'major': 0, 'minor': 1, 'patch': 2}[position]
+    latest_version[position_index] += 1
+    latest_version[position_index + 1:] = [0] * (2 - position_index)
+    return tuple(latest_version)
+
+
+def _version_format(version):
+    """Return version in dotted string format."""
+    return '.'.join(str(x) for x in version)
+
+
+def _patch_file(file_path, line_callback):
+    """Patch given file with result from line callback.
+
+    Each line will be passed to the line callback.
+    The return value of the given callback will determine
+    the new content for the file.
+
+    :param str file_path:
+        The file to patch.
+    :param callable line_callback:
+        The patch function to run over each line.
+    :return:
+        Whenever the file has changed or not.
+    :rtype:
+        bool
+    """
+    new_file_content, file_changed = [], False
+    with _path_open(file_path) as in_stream:
+        for l in (x.strip('\n') for x in in_stream):
+            alt_lines = line_callback(l) or [l]
+            if alt_lines != [l]:
+                file_changed = True
+            new_file_content += (x + u'\n' for x in alt_lines)
+
+    new_file_name = file_path + '.new'
+    with _path_open(new_file_name, mode='w') as out_stream:
+        out_stream.writelines(new_file_content)
+        out_stream.flush()
+        os.fsync(out_stream.fileno())
+    os.rename(new_file_name, file_path)
+
+    return file_changed
+
+
+def _patch_version(new_version):
+    """Patch given version into version file."""
+    _patch_version_re = re.compile(r"""^(\s*__version__\s*=\s*(?:"|'))"""
+                                   r"""(?:[^'"]*)(?:("|')\s*)$""")
+
+    def __line_callback(line):
+        match = _patch_version_re.match(line)
+        if match:
+            line_head, line_tail = match.groups()
+            return [line_head + new_version + line_tail]
+    return _patch_file(VERSION_FILE, __line_callback)
+
+
+def _patch_change_log(new_version):
+    """Patch given version into change log file."""
+    def __line_callback(line):
+        if line == u'`Unreleased`_':
+            return [u'`{}`_'.format(new_version)]
+        elif line == u'Yet to be released.':
+            return [datetime.utcnow().strftime(u'Released on %F.')]
+        elif line == u'.. %UNRELEASED_SECTION%':
+            return [u'.. %UNRELEASED_SECTION%',
+                    u'',
+                    u'`Unreleased`_',
+                    u'-------------',
+                    u'',
+                    u'Yet to be released.',
+                    u'']
+    return _patch_file(CHANGE_LOG_FILE, __line_callback)
+
+
+ at task()
+def mkrelease(position='minor'):
+    """Merge development state into Master Branch and tags a new Release."""
+    next_version = _version_format(_version_guess_next(position))
+    with _git_work_on('develop'):
+        patched_files = []
+        if _patch_version(next_version):
+            patched_files.append(VERSION_FILE)
+
+        if _patch_change_log(next_version):
+            patched_files.append(CHANGE_LOG_FILE)
+
+        if patched_files:
+            patched_files = ' '.join(_shell_quote(x) for x in patched_files)
+            _git_do('diff --color=always -- ' + patched_files,
+                    ("commit -m 'Bump Version to {0}' -- {1}"
+                     .format(next_version, patched_files)),
+                    hide=None)
+
+        with _git_work_on('master'):
+            message = _shell_quote('Release {0}'.format(next_version))
+            _git_do('merge --no-ff --no-edit -m {0} develop'.format(message),
+                    "tag -a -m {0} {1}".format(message, next_version))
diff --git a/test_pytest_catchlog.py b/test_pytest_catchlog.py
index 0e9d55f..b7d6b3b 100644
--- a/test_pytest_catchlog.py
+++ b/test_pytest_catchlog.py
@@ -1,6 +1,6 @@
 import py
 
-pytest_plugins = 'pytester', 'catchlog'
+pytest_plugins = 'pytester'
 
 
 def test_nothing_logged(testdir):
@@ -8,8 +8,6 @@ def test_nothing_logged(testdir):
         import sys
         import logging
 
-        pytest_plugins = 'catchlog'
-
         def test_foo():
             sys.stdout.write('text going to stdout')
             sys.stderr.write('text going to stderr')
@@ -22,7 +20,7 @@ def test_nothing_logged(testdir):
     result.stdout.fnmatch_lines(['*- Captured stderr call -*',
                                  'text going to stderr'])
     py.test.raises(Exception, result.stdout.fnmatch_lines,
-                   ['*- Captured log -*'])
+                   ['*- Captured *log call -*'])
 
 
 def test_messages_logged(testdir):
@@ -30,8 +28,6 @@ def test_messages_logged(testdir):
         import sys
         import logging
 
-        pytest_plugins = 'catchlog'
-
         def test_foo():
             sys.stdout.write('text going to stdout')
             sys.stderr.write('text going to stderr')
@@ -40,7 +36,7 @@ def test_messages_logged(testdir):
         ''')
     result = testdir.runpytest()
     assert result.ret == 1
-    result.stdout.fnmatch_lines(['*- Captured log -*',
+    result.stdout.fnmatch_lines(['*- Captured *log call -*',
                                  '*text going to logger*'])
     result.stdout.fnmatch_lines(['*- Captured stdout call -*',
                                  'text going to stdout'])
@@ -48,12 +44,50 @@ def test_messages_logged(testdir):
                                  'text going to stderr'])
 
 
-def test_change_level(testdir):
+def test_setup_logging(testdir):
+    testdir.makepyfile('''
+        import sys
+        import logging
+
+        def setup_function(function):
+            logging.getLogger().info('text going to logger from setup')
+
+        def test_foo():
+            logging.getLogger().info('text going to logger from call')
+            assert False
+        ''')
+    result = testdir.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured *log setup -*',
+                                 '*text going to logger from setup*',
+                                 '*- Captured *log call -*',
+                                 '*text going to logger from call*'])
+
+
+def test_teardown_logging(testdir):
     testdir.makepyfile('''
         import sys
         import logging
 
-        pytest_plugins = 'catchlog'
+        def test_foo():
+            logging.getLogger().info('text going to logger from call')
+
+        def teardown_function(function):
+            logging.getLogger().info('text going to logger from teardown')
+            assert False
+        ''')
+    result = testdir.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured *log call -*',
+                                 '*text going to logger from call*',
+                                 '*- Captured *log teardown -*',
+                                 '*text going to logger from teardown*'])
+
+
+def test_change_level(testdir):
+    testdir.makepyfile('''
+        import sys
+        import logging
 
         def test_foo(caplog):
             caplog.set_level(logging.INFO)
@@ -70,13 +104,13 @@ def test_change_level(testdir):
         ''')
     result = testdir.runpytest()
     assert result.ret == 1
-    result.stdout.fnmatch_lines(['*- Captured log -*',
+    result.stdout.fnmatch_lines(['*- Captured *log call -*',
                                  '*handler INFO level*',
                                  '*logger CRITICAL level*'])
     py.test.raises(Exception, result.stdout.fnmatch_lines,
-                   ['*- Captured log -*', '*handler DEBUG level*'])
+                   ['*- Captured *log call -*', '*handler DEBUG level*'])
     py.test.raises(Exception, result.stdout.fnmatch_lines,
-                   ['*- Captured log -*', '*logger WARNING level*'])
+                   ['*- Captured *log call -*', '*logger WARNING level*'])
 
 
 @py.test.mark.skipif('sys.version_info < (2,5)')
@@ -86,8 +120,6 @@ def test_with_statement(testdir):
         import sys
         import logging
 
-        pytest_plugins = 'catchlog'
-
         def test_foo(caplog):
             with caplog.at_level(logging.INFO):
                 log = logging.getLogger()
@@ -103,13 +135,13 @@ def test_with_statement(testdir):
         ''')
     result = testdir.runpytest()
     assert result.ret == 1
-    result.stdout.fnmatch_lines(['*- Captured log -*',
+    result.stdout.fnmatch_lines(['*- Captured *log call -*',
                                  '*handler INFO level*',
                                  '*logger CRITICAL level*'])
     py.test.raises(Exception, result.stdout.fnmatch_lines,
-                   ['*- Captured log -*', '*handler DEBUG level*'])
+                   ['*- Captured *log call -*', '*handler DEBUG level*'])
     py.test.raises(Exception, result.stdout.fnmatch_lines,
-                   ['*- Captured log -*', '*logger WARNING level*'])
+                   ['*- Captured *log call -*', '*logger WARNING level*'])
 
 
 def test_log_access(testdir):
@@ -117,13 +149,11 @@ def test_log_access(testdir):
         import sys
         import logging
 
-        pytest_plugins = 'catchlog'
-
         def test_foo(caplog):
             logging.getLogger().info('boo %s', 'arg')
-            assert caplog.records()[0].levelname == 'INFO'
-            assert caplog.records()[0].msg == 'boo %s'
-            assert 'boo arg' in caplog.text()
+            assert caplog.records[0].levelname == 'INFO'
+            assert caplog.records[0].msg == 'boo %s'
+            assert 'boo arg' in caplog.text
         ''')
     result = testdir.runpytest()
     assert result.ret == 0
@@ -139,12 +169,10 @@ def test_record_tuples(testdir):
         import sys
         import logging
 
-        pytest_plugins = 'catchlog'
-
         def test_foo(caplog):
             logging.getLogger().info('boo %s', 'arg')
 
-            assert caplog.record_tuples() == [
... 109 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