# Copyright (C) 2007-2012 Michael Foord & the mock team
# E-mail: fuzzyman AT voidspace DOT org DOT uk
# http://www.voidspace.org.uk/python/mock/

import os
import sys

import unittest
from unittest.test.testmock import support
from unittest.test.testmock.support import SomeClass, is_instance

from test.test_importlib.util import uncache
from unittest.mock import (
    NonCallableMock, CallableMixin, sentinel,
    MagicMock, Mock, NonCallableMagicMock, patch, _patch,
    DEFAULT, call, _get_target
)


builtin_string = 'builtins'

PTModule = sys.modules[__name__]
MODNAME = '%s.PTModule' % __name__


def _get_proxy(obj, get_only=True):
    class Proxy(object):
        def __getattr__(self, name):
            return getattr(obj, name)
    if not get_only:
        def __setattr__(self, name, value):
            setattr(obj, name, value)
        def __delattr__(self, name):
            delattr(obj, name)
        Proxy.__setattr__ = __setattr__
        Proxy.__delattr__ = __delattr__
    return Proxy()


# for use in the test
something  = sentinel.Something
something_el
se  = sentinel.SomethingElse


class Foo(object):
    def __init__(self, a):
        pass
    def f(self, a):
        pass
    def g(self):
        pass
    foo = 'bar'

    class Bar(object):
        def a(self):
            pass

foo_name = '%s.Foo' % __name__


def function(a, b=Foo):
    pass


class Container(object):
  
  def __init__(self):
        self.values = {}

    def __getitem__(self, name):
        return self.values[name]

    def __setitem__(self, name, value):
        self.values[name] = value

    def __delitem__(self, name):
        del self.values[name]

    def __iter__(self):
        return iter(self.values)



class PatchTest(unittest.TestCase):

    def assertNotCallable(self, obj, magic=True):
        MockClass = NonCallableMagicMock
        if not magic:
            MockClass = NonCallableMock

        self.assertRaises(TypeError, obj)
        self.assertTrue(is_instance(obj, MockClass))
        self.assertFalse(is_instance(obj, CallableMixin))


    def test_single_patchobject(self):
        class Something(object):
            attribute = sentinel.Original

        @patch.object(Something, 'attribute', sentinel.Patched)
        def test():
            self.assertEqual(Something.attribute, sentinel.Patched, "unpatched")

        test()
        self.assertEqual(Something.attribute, sentinel.Original,
                         "patch not restored")


    def test_patchobject_with_none(self):
        class Something(object):
            attribute = sentinel.Original

        @patch.object(Something, 'attribute', None)
        def test():
            self.assertIsNone(Something.attribute, "unpatched")

        test()
        self.assertEqual(Something.attribute, sentinel.Original,
                         "patch not restored")


    def test_multiple_patchobject(self):
        class Something(object):
            attribute = sentinel.Original
            next_attribute = sentinel.Original2

        @patch.object(Something, 'attribute', sentinel.Patched)
        @patch.object(Something, 'next_attribute', sentinel.Patched2)
        def test():
            self.assertEqual(Something.attribute, sentinel.Patched,
                             "unpatched")
            self.assertEqual(Something.next_attribute, sentinel.Patched2,
                             "unpatched")

        test()
        self.assertEqual(Something.attribute, sentinel.Original,
                         "patch not restored")
        self.assertEqual(Something.next_attribute, sentinel.Original2,
                         "patch not restored")


    def test_object_lookup_is_quite_lazy(self):
        global something
        original = something
        @patch('%s.something' % __name__, sentinel.Something2)
        def test():
            pass

        try:
            something = sentinel.replacement_value
            test()
            self.assertEqual(something, sentinel.replacement_value)
        finally:
            something = original


    def test_patch(self):
        @patch('%s.something' % __name__, sentinel.Something2)
        def test():
            self.assertEqual(PTModule.something, sentinel.Something2,
                             "unpatched")

        test()
        self.assertEqual(PTModule.something, sentinel.Something,
                         "patch not restored")

        @patch('%s.something' % __name__, sentinel.Something2)
        @patch('%s.something_else' % __name__, sentinel.SomethingElse)
        def test():
            self.assertEqual(PTModule.something, sentinel.Something2,
                             "unpatched")
            self.assertEqual(PTModule.something_else, sentinel.SomethingElse,
                             "unpatched")

        self.assertEqual(PTModule.something, sentinel.Something,
                         "patch not restored")
        self.assertEqual(PTModule.something_else, sentinel.SomethingElse,
                         "patch not restored")

        # Test the patching and restoring works a second time
        test()

        self.assertEqual(PTModule.something, sentinel.Something,
                         "patch not restored")
        self.assertEqual(PTModule.something_else, sentinel.SomethingElse,
                         "patch not restored")

        mock = Mock()
        mock.return_value = sentinel.Handle
        @patch('%s.open' % builtin_string, mock)
        def test():
            self.assertEqual(open('filename', 'r'), sentinel.Handle,
                             "open not patched")
        test()
        test()

        self.assertNotEqual(open, mock, "patch not restored")


    def test_patch_class_attribute(self):
        @patch('%s.SomeClass.class_attribute' % __name__,
               sentinel.ClassAttribute)
        def test():
            self.assertEqual(PTModule.SomeClass.class_attribute,
                             sentinel.ClassAttribute, "unpatched")
        test()

        self.assertIsNone(PTModule.SomeClass.class_attribute,
                          "patch not restored")


    def test_patchobject_with_default_mock(self):
        class Test(object):
            something = sentinel.Original
            something2 = sentinel.Original2

        @patch.object(Test, 'something')
        def test(mock):
            self.assertEqual(mock, Test.something,
                             "Mock not passed into test function")
            self.assertIsInstance(mock, MagicMock,
                            "patch with two arguments did not create a mock")

        test()

        @patch.object(Test, 'something')
        @patch.object(Test, 'something2')
        def test(this1, this2, mock1, mock2):
            self.assertEqual(this1, sentinel.this1,
                             "Patched function didn't receive initial argument")
            self.assertEqual(this2, sentinel.this2,
                             "Patched function didn't receive second argument")
            self.assertEqual(mock1, Test.something2,
                             "Mock not passed into test function")
            self.assertEqual(mock2, Test.something,
                             "Second Mock not passed into test function")
            self.assertIsInstance(mock2, MagicMock,
                            "patch with two arguments did not create a mock")
            self.assertIsInstance(mock2, MagicMock,
                            "patch with two arguments did not create a mock")

            # A hack to test that new mocks are passed the second time
            self.assertNotEqual(outerMock1, mock1, "unexpected value for mock1")
            self.assertNotEqual(outerMock2, mock2, "unexpected value for mock1")
            return mock1, mock2

        outerMock1 = outerMock2 = None
        outerMock1, outerMock2 = test(sentinel.this1, sentinel.this2)

        # Test that executing a second time creates new mocks
        test(sentinel.this1, sentinel.this2)


    def test_patch_with_spec(self):
        @patch('%s.SomeClass' % __name__, spec=SomeClass)
        def test(MockSomeClass):
            self.assertEqual(SomeClass, MockSomeClass)
       