# fixtures: Fixtures with cleanups for testing and convenience. # # Copyright (c) 2010, Robert Collins # # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause # license at the users choice. A copy of both licenses are available in the # project source as Apache-2.0 and BSD. You may not use this file except in # compliance with one of these two licences. # # Unless required by applicable law or agreed to in writing, software # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # license you chose for the specific language governing permissions and # limitations under that license. import functools import sys import testtools from testtools.matchers import Is from fixtures import MonkeyPatch, TestWithFixtures reference = 23 NEW_PY39_CLASSMETHOD = ( sys.version_info[:2] in ((3, 9), (3,10)) and not hasattr(sys, "pypy_version_info")) class C(object): def foo(self, arg): return arg @staticmethod def foo_static(): pass @classmethod def foo_cls(cls): pass class D(object): def bar(self): pass def bar_two_args(self, arg=None): return (self, arg) @classmethod def bar_cls(cls): return cls @classmethod def bar_cls_args(cls, *args): return (cls,) + args @staticmethod def bar_static(): pass @staticmethod def bar_static_args(*args): return args def bar_self_referential(self, *args, **kwargs): self.bar() INST_C = C() def fake(*args): return args def fake2(arg): pass def fake_no_args(): pass def fake_no_args2(): pass @staticmethod def fake_static(): pass class TestMonkeyPatch(testtools.TestCase, TestWithFixtures): def test_patch_and_restore(self): fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.reference', 45) self.assertEqual(23, reference) fixture.setUp() try: self.assertEqual(45, reference) finally: fixture.cleanUp() self.assertEqual(23, reference) def test_patch_missing_attribute(self): fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.new_attr', True) self.assertFalse('new_attr' in globals()) fixture.setUp() try: self.assertEqual(True, new_attr) finally: fixture.cleanUp() self.assertFalse('new_attr' in globals()) def test_delete_existing_attribute(self): fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.reference', MonkeyPatch.delete) self.assertEqual(23, reference) fixture.setUp() try: self.assertFalse('reference' in globals()) finally: fixture.cleanUp() self.assertEqual(23, reference) def test_delete_missing_attribute(self): fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.new_attr', MonkeyPatch.delete) self.assertFalse('new_attr' in globals()) fixture.setUp() try: self.assertFalse('new_attr' in globals()) finally: fixture.cleanUp() self.assertFalse('new_attr' in globals()) def _check_restored_static_or_class_method(self, oldmethod, oldmethod_inst, klass, methodname): restored_method = getattr(klass, methodname) restored_method_inst = getattr(klass(), methodname) self.assertEqual(oldmethod, restored_method) self.assertEqual(oldmethod, restored_method_inst) self.assertEqual(oldmethod_inst, restored_method) self.assertEqual(oldmethod_inst, restored_method_inst) restored_method() restored_method_inst() def test_patch_staticmethod_with_staticmethod(self): oldmethod = C.foo_static oldmethod_inst = C().foo_static fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_static', D.bar_static) with fixture: C.foo_static() C().foo_static() self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_static') def test_patch_staticmethod_with_classmethod(self): oldmethod = C.foo_static oldmethod_inst = C().foo_static fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_static', D.bar_cls) with fixture: C.foo_static() C().foo_static() self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_static') def test_patch_staticmethod_with_function(self): oldmethod = C.foo_static oldmethod_inst = C().foo_static fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_static', fake_no_args) with fixture: C.foo_static() C().foo_static() self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_static') def test_patch_staticmethod_with_boundmethod(self): oldmethod = C.foo_static oldmethod_inst = C().foo_static fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_static', D().bar) with fixture: C.foo_static() C().foo_static() self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_static') def test_patch_classmethod_with_staticmethod(self): oldmethod = C.foo_cls oldmethod_inst = C().foo_cls fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_cls', D.bar_static_args) with fixture: (cls,) = C.foo_cls() self.expectThat(cls, Is(C)) (cls,) = C().foo_cls() self.expectThat(cls, Is(C)) self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_cls') def test_patch_classmethod_with_classmethod(self): oldmethod = C.foo_cls oldmethod_inst = C().foo_cls fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_cls', D.bar_cls_args) with fixture: # Python 3.9 changes the behavior of the classmethod decorator so # that it now honours the descriptor binding protocol [1]. # This means we're now going to call the monkeypatched classmethod # the way we would have if it hadn't been monkeypatched: simply # with the class # # https://bugs.python.org/issue19072 if NEW_PY39_CLASSMETHOD: cls, = C.foo_cls() self.expectThat(cls, Is(D)) cls, = C().foo_cls() self.expectThat(cls, Is(D)) else: cls, target_class = C.foo_cls() self.expectThat(cls, Is(D)) self.expectThat(target_class, Is(C)) cls, target_class = C().foo_cls() self.expectThat(cls, Is(D)) self.expectThat(target_class, Is(C)) self._check_restored_static_or_class_method( oldmethod, oldmethod_inst, C, 'foo_cls') def test_patch_classmethod_with_function(self): oldmethod = C.foo_cls oldmethod_inst = C().foo_cls fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_cls', fake) with fixture: (cls,) = C.foo_cls() self.expectThat(cls, Is(C)) (cls, arg) = C().foo_cls(1) self.expectThat(cls, Is(C)) self.assertEqual(1, arg) self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_cls') def test_patch_classmethod_with_boundmethod(self): oldmethod = C.foo_cls oldmethod_inst = C().foo_cls d = D() fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_cls', d.bar_two_args) with fixture: slf, cls = C.foo_cls() self.expectThat(slf, Is(d)) # See note in test_patch_classmethod_with_classmethod on changes in # Python 3.9 if NEW_PY39_CLASSMETHOD: self.expectThat(cls, Is(None)) else: self.expectThat(cls, Is(C)) slf, cls = C().foo_cls() self.expectThat(slf, Is(d)) if NEW_PY39_CLASSMETHOD: self.expectThat(cls, Is(None)) else: self.expectThat(cls, Is(C)) self._check_restored_static_or_class_method( oldmethod, oldmethod_inst, C, 'foo_cls') def test_patch_function_with_staticmethod(self): oldmethod = fake_no_args fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.fake_no_args', D.bar_static) with fixture: fake_no_args() self.assertEqual(oldmethod, fake_no_args) def test_patch_function_with_classmethod(self): oldmethod = fake_no_args fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.fake_no_args', D.bar_cls) with fixture: fake_no_args() self.assertEqual(oldmethod, fake_no_args) def test_patch_function_with_function(self): oldmethod = fake_no_args fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.fake_no_args', fake_no_args2) with fixture: fake_no_args() self.assertEqual(oldmethod, fake_no_args) def test_patch_function_with_partial(self): oldmethod = fake_no_args fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.fake_no_args', functools.partial(fake, 1)) with fixture: (ret,) = fake_no_args() self.assertEqual(1, ret) self.assertEqual(oldmethod, fake_no_args) def test_patch_function_with_boundmethod(self): oldmethod = fake_no_args fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.fake_no_args', D().bar) with fixture: fake_no_args() self.assertEqual(oldmethod, fake_no_args) def test_patch_boundmethod_with_staticmethod(self): oldmethod = INST_C.foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.INST_C.foo', D.bar_static) with fixture: INST_C.foo() self.assertEqual(oldmethod, INST_C.foo) def test_patch_boundmethod_with_classmethod(self): oldmethod = INST_C.foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.INST_C.foo', D.bar_cls) with fixture: INST_C.foo() self.assertEqual(oldmethod, INST_C.foo) def test_patch_boundmethod_with_function(self): oldmethod = INST_C.foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.INST_C.foo', fake_no_args) with fixture: INST_C.foo() self.assertEqual(oldmethod, INST_C.foo) def test_patch_boundmethod_with_boundmethod(self): oldmethod = INST_C.foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.INST_C.foo', D().bar) with fixture: INST_C.foo() self.assertEqual(oldmethod, INST_C.foo) sentinel = object() self.assertEqual(sentinel, INST_C.__dict__.get('foo', sentinel)) def test_patch_unboundmethod_with_staticmethod(self): oldmethod = C.foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo', D.bar_static_args) with fixture: target_self, arg = INST_C.foo(1) self.expectThat(target_self, Is(INST_C)) self.assertEqual(1, arg) self.assertEqual(oldmethod, C.foo) def test_patch_unboundmethod_with_classmethod(self): oldmethod = C.foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo', D.bar_cls_args) with fixture: c = C() cls, target_self, arg = c.foo(1) self.expectThat(cls, Is(D)) self.expectThat(target_self, Is(c)) self.assertEqual(1, arg) self.assertEqual(oldmethod, C.foo) def test_patch_unboundmethod_with_function(self): oldmethod = C.foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo', fake) with fixture: c = C() target_self, arg = c.foo(1) self.expectThat(target_self, Is(c)) self.assertTrue(1, arg) self.assertEqual(oldmethod, C.foo) def test_patch_unboundmethod_with_boundmethod(self): oldmethod = C.foo d = D() fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo', d.bar_two_args) with fixture: c = C() slf, target_self = c.foo() self.expectThat(slf, Is(d)) self.expectThat(target_self, Is(c)) self.assertEqual(oldmethod, C.foo) def test_double_patch_instancemethod(self): oldmethod = C.foo oldmethod_inst = C().foo fixture1 = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo', fake) fixture2 = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo', fake2) with fixture1: with fixture2: C().foo() self.assertEqual(oldmethod, C.foo) # The method address changes with each instantiation of C, and method # equivalence just tests that. Compare the code objects instead. self.assertEqual(oldmethod_inst.__code__, C().foo.__code__) def test_double_patch_staticmethod(self): oldmethod = C.foo_static oldmethod_inst = C().foo_static fixture1 = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_static', fake_no_args) fixture2 = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_static', fake_static) with fixture1: with fixture2: C.foo_static() C().foo_static() self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_static') def test_double_patch_classmethod(self): oldmethod = C.foo_cls oldmethod_inst = C().foo_cls fixture1 = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_cls', fake) fixture2 = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo_cls', fake2) with fixture1: with fixture2: C.foo_cls() C().foo_cls() self._check_restored_static_or_class_method(oldmethod, oldmethod_inst, C, 'foo_cls') def test_patch_c_foo_with_instance_d_bar_self_referential(self): oldmethod = C.foo oldmethod_inst = C().foo fixture = MonkeyPatch( 'fixtures.tests._fixtures.test_monkeypatch.C.foo', D().bar_self_referential) with fixture: C().foo() self.assertEqual(oldmethod, C.foo) # The method address changes with each instantiation of C, and method # equivalence just tests that. Compare the code objects instead. self.assertEqual(oldmethod_inst.__code__, C().foo.__code__)