##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import unittest

from persistent import Persistent
from persistent.interfaces import IPersistent

# Confusing:  ZODB doesn't use this file.  It appears to be used only
# by Zope3, where it's imported by zope/app/schema/tests/test_wrapper.py.

try:
    import zope.interface
except ImportError:
    interfaces = False
else:
    interfaces = True

class Test(unittest.TestCase):

    klass = None # override in subclass

    def testSaved(self):
        p = self.klass()
        p._p_oid = '\0\0\0\0\0\0hi'
        dm = DM()
        p._p_jar = dm
        self.assertEqual(p._p_changed, 0)
        self.assertEqual(dm.called, 0)
        p.inc()
        self.assertEqual(p._p_changed, 1)
        self.assertEqual(dm.called, 1)
        p.inc()
        self.assertEqual(p._p_changed, 1)
        self.assertEqual(dm.called, 1)
        p._p_deactivate()
        self.assertEqual(p._p_changed, 1)
        self.assertEqual(dm.called, 1)
        p._p_deactivate()
        self.assertEqual(p._p_changed, 1)
        self.assertEqual(dm.called, 1)
        del p._p_changed
        # deal with current cPersistence implementation
        if p._p_changed != 3:
            self.assertEqual(p._p_changed, None)
        self.assertEqual(dm.called, 1)
        p.inc()
        self.assertEqual(p.x, 43)
        self.assertEqual(p._p_changed, 1)
        self.assertEqual(dm.called, 2)
        p._p_changed = 0
        self.assertEqual(p._p_changed, 0)
        self.assertEqual(dm.called, 2)
        self.assertEqual(p.x, 43)
        p.inc()
        self.assertEqual(p._p_changed, 1)
        self.assertEqual(dm.called, 3)

    def testUnsaved(self):
        p = self.klass()

        self.assertEqual(p.x, 0)
        self.assertEqual(p._p_changed, 0)
        self.assertEqual(p._p_jar, None)
        self.assertEqual(p._p_oid, None)
        p.inc()
        p.inc()
        self.assertEqual(p.x, 2)
        self.assertEqual(p._p_changed, 0)

        p._p_deactivate()
        self.assertEqual(p._p_changed, 0)
        p._p_changed = 1
        self.assertEqual(p._p_changed, 0)
        p._p_deactivate()
        self.assertEqual(p._p_changed, 0)
        del p._p_changed
        self.assertEqual(p._p_changed, 0)
        if self.has_dict:
            self.failUnless(p.__dict__)
        self.assertEqual(p.x, 2)

    def testState(self):
        p = self.klass()
        self.assertEqual(p.__getstate__(), {'x': 0})
        self.assertEqual(p._p_changed, 0)
        p.__setstate__({'x':5})
        self.assertEqual(p._p_changed, 0)
        if self.has_dict:
            p._v_foo = 2
        self.assertEqual(p.__getstate__(), {'x': 5})
        self.assertEqual(p._p_changed, 0)

    def testSetStateSerial(self):
        p = self.klass()
        p._p_serial = '00000012'
        p.__setstate__(p.__getstate__())
        self.assertEqual(p._p_serial, '00000012')

    def testDirectChanged(self):
        p = self.klass()
        p._p_oid = 1
        dm = DM()
        p._p_jar = dm
        self.assertEqual(p._p_changed, 0)
        self.assertEqual(dm.called, 0)
        p._p_changed = 1
        self.assertEqual(dm.called, 1)

    def testGhostChanged(self):
        # If an object is a ghost and its _p_changed is set to True (any
        # true value), it should activate (unghostify) the object.  This
        # behavior is new in ZODB 3.6; before then, an attempt to do
        # "ghost._p_changed = True" was ignored.
        p = self.klass()
        p._p_oid = 1
        dm = DM()
        p._p_jar = dm
        p._p_deactivate()
        self.assertEqual(p._p_changed, None)
        p._p_changed = True
        self.assertEqual(p._p_changed, 1)

    def testRegistrationFailure(self):
        p = self.klass()
        p._p_oid = 1
        dm = BrokenDM()
        p._p_jar = dm
        self.assertEqual(p._p_changed, 0)
        self.assertEqual(dm.called, 0)
        try:
            p._p_changed = 1
        except NotImplementedError:
            pass
        else:
            raise AssertionError("Exception not propagated")
        self.assertEqual(dm.called, 1)
        self.assertEqual(p._p_changed, 0)

    def testLoadFailure(self):
        p = self.klass()
        p._p_oid = 1
        dm = BrokenDM()
        p._p_jar = dm
        p._p_deactivate()  # make it a ghost

        try:
            p._p_activate()
        except NotImplementedError:
            pass
        else:
            raise AssertionError("Exception not propagated")
        self.assertEqual(p._p_changed, None)

    def testActivate(self):
        p = self.klass()
        dm = DM()
        p._p_oid = 1
        p._p_jar = dm
        p._p_changed = 0
        p._p_deactivate()
        # Unsure: does this really test the activate method?
        p._p_activate()
        self.assertEqual(p._p_changed, 0)
        self.assertEqual(p.x, 42)

    def testDeactivate(self):
        p = self.klass()
        dm = DM()
        p._p_oid = 1
        p._p_deactivate() # this deactive has no effect
        self.assertEqual(p._p_changed, 0)
        p._p_jar = dm
        p._p_changed = 0
        p._p_deactivate()
        self.assertEqual(p._p_changed, None)
        p._p_activate()
        self.assertEqual(p._p_changed, 0)
        self.assertEqual(p.x, 42)

    if interfaces:
        def testInterface(self):
            self.assert_(IPersistent.implementedBy(Persistent),
                         "%s does not implement IPersistent" % Persistent)
            p = Persistent()
            self.assert_(IPersistent.providedBy(p),
                         "%s does not implement IPersistent" % p)

            self.assert_(IPersistent.implementedBy(P),
                         "%s does not implement IPersistent" % P)
            p = self.klass()
            self.assert_(IPersistent.providedBy(p),
                         "%s does not implement IPersistent" % p)

    def testDataManagerAndAttributes(self):
        # Test to cover an odd bug where the instance __dict__ was
        # set at the same location as the data manager in the C type.
        p = P()
        p.inc()
        p.inc()
        self.assert_('x' in p.__dict__)
        self.assert_(p._p_jar is None)

    def testMultipleInheritance(self):
        # make sure it is possible to inherit from two different
        # subclasses of persistent.
        class A(Persistent):
            pass
        class B(Persistent):
            pass
        class C(A, B):
            pass
        class D(object):
            pass
        class E(D, B):
            pass

    def testMultipleMeta(self):
        # make sure it's possible to define persistent classes
        # with a base whose metaclass is different
        class alternateMeta(type):
            pass
        class alternate(object):
            __metaclass__ = alternateMeta
        class mixedMeta(alternateMeta, type):
            pass
        class mixed(alternate,Persistent):
            __metaclass__ = mixedMeta

    def testSlots(self):
        # Verify that Persistent classes behave the same way
        # as pure Python objects where '__slots__' and '__dict__'
        # are concerned.

        class noDict(object):
            __slots__ = ['foo']

        class shouldHaveDict(noDict):
            pass

        class p_noDict(Persistent):
            __slots__ = ['foo']

        class p_shouldHaveDict(p_noDict):
            pass

        self.assertEqual(noDict.__dictoffset__, 0)
        self.assertEqual(p_noDict.__dictoffset__, 0)

        self.assert_(shouldHaveDict.__dictoffset__ <> 0)
        self.assert_(p_shouldHaveDict.__dictoffset__ <> 0)

    def testBasicTypeStructure(self):
        # test that a persistent class has a sane C type structure
        # use P (defined below) as simplest example
        self.assertEqual(Persistent.__dictoffset__, 0)
        self.assertEqual(Persistent.__weakrefoffset__, 0)
        self.assert_(Persistent.__basicsize__ > object.__basicsize__)
        self.assert_(P.__dictoffset__)
        self.assert_(P.__weakrefoffset__)
        self.assert_(P.__dictoffset__ < P.__weakrefoffset__)
        self.assert_(P.__basicsize__ > Persistent.__basicsize__)

# Unsure:  Can anyone defend/explain the test below? The tests classes defined
# here don't define __call__, so this weird test will always pass, but to what
# end?  If a klass is given that happens to define __call__, the test *may*
# mysteriously fail. Who cares?

##     def testDeactivateErrors(self):
##         p = self.klass()
##         p._p_oid = '\0\0\0\0\0\0hi'
##         dm = DM()
##         p._p_jar = dm

##         def typeerr(*args, **kwargs):
##             self.assertRaises(TypeError, p, *args, **kwargs)

##         typeerr(1)
##         typeerr(1, 2)
##         typeerr(spam=1)
##         typeerr(spam=1, force=1)

##         p._p_changed = True
##         class Err(object):
##             def __nonzero__(self):
##                 raise RuntimeError

##         typeerr(force=Err())

class P(Persistent):
    def __init__(self):
        self.x = 0
    def inc(self):
        self.x += 1

class P2(P):
    def __getstate__(self):
        return 42
    def __setstate__(self, v):
        self.v = v

class B(Persistent):

    __slots__ = ["x", "_p_serial"]

    def __init__(self):
        self.x = 0

    def inc(self):
        self.x += 1

    def __getstate__(self):
        return {'x': self.x}

    def __setstate__(self, state):
        self.x = state['x']

class DM:
    def __init__(self):
        self.called = 0
    def register(self, ob):
        self.called += 1
    def setstate(self, ob):
        ob.__setstate__({'x': 42})

class BrokenDM(DM):

    def register(self,ob):
        self.called += 1
        raise NotImplementedError

    def setstate(self,ob):
        raise NotImplementedError

class PersistentTest(Test):
    klass = P
    has_dict = 1

    def testPicklable(self):
        import pickle

        p = self.klass()
        p.inc()
        p2 = pickle.loads(pickle.dumps(p))
        self.assertEqual(p2.__class__, self.klass)

        # verify that the inc is reflected:
        self.assertEqual(p2.x, p.x)

        # This assertion would be invalid.  Interfaces
        # are compared by identity and copying doesn't
        # preserve identity. We would get false negatives due
        # to the differing identities of the original and copied
        # PersistentInterface:
        # self.assertEqual(p2.__dict__, p.__dict__)

    def testPicklableWCustomState(self):
        import pickle

        p = P2()
        p2 = pickle.loads(pickle.dumps(p))
        self.assertEqual(p2.__class__, P2);
        self.assertEqual(p2.__dict__, {'v': 42})

class BasePersistentTest(Test):
    klass = B
    has_dict = 0
