1
0
mirror of https://gerrit.ovirt.org/vdsm synced 2026-02-05 12:46:23 +01:00
Files
vdsm/tests/monkeypatch.py
Dan Kenigsberg c81f6ea536 whitespace: add a whitespace before first import
Nir prefers it like that, I don't mind.

perl -0777 -i.original -pe 's/#\nfrom __future__/#\n\nfrom __future__/' `git ls-files \*.py`

is easy.

Change-Id: Iad69872e6ae1de62e7f2081da44bb92963eb358c
Signed-off-by: Dan Kenigsberg <danken@redhat.com>
2018-10-06 11:36:19 +03:00

204 lines
5.5 KiB
Python

#
# Copyright 2012-2017 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Refer to the README and COPYING files for full details of the license
#
from __future__ import absolute_import
from __future__ import division
from contextlib import contextmanager
from functools import wraps
import inspect
import six
#
# Monkey patch.
#
# Usage:
# ---
# import monkeypatch
#
# class TestSomething():
#
# def __init__(self):
# self.patch = monkeypatch.Patch([
# (subprocess, 'Popen', lambda x: None),
# (os, 'chown', lambda *x: 0)
# ])
#
# def setUp(self):
# self.patch.apply()
#
# def tearDown(self):
# self.patch.revert()
#
# def testThis(self):
# # using patched functions
#
# def testThat(self):
# # using patched functions
# ---
#
class Patch(object):
def __init__(self, what):
self.what = what
self.old = []
@staticmethod
def _is_static_method(cls, method_name, method):
is_static_py2 = six.PY2 and inspect.isfunction(method)
# In Python 3, static methods are returned as 'function' and lose
# 'staticmethod' class relationship when returned by 'getattr'
# so we have to reach to __dict__ directly. Calling 'inspect.ismethod'
# to differentiate between a regular method and a static method won't
# work without referring to *bound* method and thus, creating
# an instance of a class.
is_static_py3 = six.PY3 and isinstance(cls.__dict__[method_name],
staticmethod)
return is_static_py2 or is_static_py3
@staticmethod
def _is_class_method(method):
return (inspect.ismethod(method) and
getattr(method, '__self__', None) is not None)
def apply(self):
assert self.old == []
for module, name, that in self.what:
old = getattr(module, name)
self.old.append((module, name, old))
# The following block is done so that if it is a method we are
# patching in, that it will have the same type as the method it
# replaced.
if inspect.isclass(module):
if self._is_static_method(module, name, old):
that = staticmethod(that)
elif self._is_class_method(old):
that = classmethod(that)
setattr(module, name, that)
def revert(self):
assert self.old != []
while self.old:
module, name, that = self.old.pop()
# Python 2 wrongly sets the function `that' as an instancemethod
# instead of keeping it as staticmethod.
if inspect.isclass(module) and self._is_static_method(module,
name, that):
that = staticmethod(that)
setattr(module, name, that)
#
# Monkey patch scope.
#
# Usage:
# ---
# from monkeypatch import MonkeyPatchScope
#
# def test():
# with MonkeyPatchScope([
# (subprocess, 'Popen', lambda x: None),
# (os, 'chown', lambda *x: 0)
# ])
# logic
# ---
#
@contextmanager
def MonkeyPatchScope(what):
patch = Patch(what)
patch.apply()
try:
yield {}
finally:
patch.revert()
#
# Monkey patch function decorator.
#
# Usage:
# ---
# from monkeypatch import MonkeyPatch
#
# @MonkeyPatch(subprocess, 'Popen', lambda x: None)
# @MonkeyPatch(os, 'chown', lambda *x: 0)
# def test():
# logic
# ---
#
def MonkeyPatch(module, name, that):
def decorator(f):
@wraps(f)
def wrapper(*args, **kw):
with MonkeyPatchScope([(module, name, that)]):
return f(*args, **kw)
return wrapper
return decorator
#
# Monkey patch class decorator.
#
# Usage:
# ---
# from monkeypatch import MonkeyClass
#
# @MonkeyClass(subprocess, 'Popen', lambda x: None)
# @MonkeyClass(os, 'chown', lambda *x: 0)
# class TestSomething():
#
# def testThis(self):
# # using patched functions
#
# def testThat(self):
# # using patched functions
# ---
#
def MonkeyClass(module, name, that):
def setup_decorator(func):
@wraps(func)
def setup(self, *a, **kw):
if not hasattr(self, '__monkeystack__'):
self.__monkeystack__ = []
patch = Patch([(module, name, that)])
self.__monkeystack__.append(patch)
patch.apply()
return func(self, *a, **kw)
return setup
def teardown_decorator(func):
@wraps(func)
def teardown(self, *a, **kw):
patch = self.__monkeystack__.pop()
patch.revert()
return func(self, *a, **kw)
return teardown
def wrapper(cls):
cls.setUp = setup_decorator(cls.setUp)
cls.tearDown = teardown_decorator(cls.tearDown)
return cls
return wrapper