Claudiu Popa fbce2bad36 Add testutils.ConfPatcher and use it in tests
The tests were modifying a global ConfigOpts object, without
resetting the old value, which could affect other tests as well.
The new ConfPatcher class can be used both as a context manager
and as a decorator, making sure that the old value of configuration
option is set back after exiting from the decorated function or from
the context manager.

Change-Id: If23bf225207977e0e313dc806d53bca8a40215d6
2015-02-12 19:43:16 +02:00

148 lines
4.3 KiB
Python

# Copyright 2014 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import contextlib
import functools
import logging as base_logging
import os
import shutil
import tempfile
from oslo.config import cfg
from cloudbaseinit.openstack.common import log as logging
CONF = cfg.CONF
__all__ = (
'create_tempfile',
'create_tempdir',
'LogSnatcher',
'ConfPatcher',
)
@contextlib.contextmanager
def create_tempdir():
"""Create a temporary directory.
This is a context manager, which creates a new temporary
directory and removes it when exiting from the context manager
block.
"""
tempdir = tempfile.mkdtemp(prefix="cloudbaseinit-tests")
try:
yield tempdir
finally:
shutil.rmtree(tempdir)
@contextlib.contextmanager
def create_tempfile(content=None):
"""Create a temporary file.
This is a context manager, which uses `create_tempdir` to obtain a
temporary directory, where the file will be placed.
:param content:
Additionally, a string which will be written
in the new file.
"""
with create_tempdir() as temp:
fd, path = tempfile.mkstemp(dir=temp)
os.close(fd)
if content:
with open(path, 'w') as stream:
stream.write(content)
yield path
# This is similar with unittest.TestCase.assertLogs from Python 3.4.
class SnatchHandler(base_logging.Handler):
def __init__(self, *args, **kwargs):
super(SnatchHandler, self).__init__(*args, **kwargs)
self.output = []
def emit(self, record):
msg = self.format(record)
self.output.append(msg)
class LogSnatcher(object):
"""A context manager to capture emitted logged messages.
The class can be used as following::
with LogSnatcher('plugins.windows.createuser') as snatcher:
LOG.info("doing stuff")
LOG.info("doing stuff %s", 1)
LOG.warn("doing other stuff")
...
self.assertEqual(snatcher.output,
['INFO:unknown:doing stuff',
'INFO:unknown:doing stuff 1',
'WARN:unknown:doing other stuff'])
"""
@property
def output(self):
return self._snatch_handler.output
def __init__(self, logger_name):
self._logger_name = logger_name
self._snatch_handler = SnatchHandler()
self._logger = logging.getLogger(self._logger_name)
self._previous_level = self._logger.logger.getEffectiveLevel()
def __enter__(self):
self._logger.logger.setLevel(base_logging.DEBUG)
self._logger.handlers.append(self._snatch_handler)
return self
def __exit__(self, *args):
self._logger.handlers.remove(self._snatch_handler)
self._logger.logger.setLevel(self._previous_level)
class ConfPatcher(object):
"""Override the configuration for the given key, with the given value.
This class can be used both as a context manager and as a decorator.
"""
# TODO(cpopa): mock.patch.dict would have been a better solution
# but oslo.config.cfg doesn't support item
# assignment.
def __init__(self, key, value, conf=CONF):
self._original_value = conf.get(key)
self._key = key
self._value = value
self._conf = conf
def __call__(self, func, *args, **kwargs):
def _wrapped_f(*args, **kwargs):
with self:
return func(*args, **kwargs)
functools.update_wrapper(_wrapped_f, func)
return _wrapped_f
def __enter__(self):
self._conf.set_override(self._key, self._value)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._conf.set_override(self._key, self._original_value)