[Commits] (donn) Updated Stamping for robustness

commits at osafoundation.org commits at osafoundation.org
Sun Aug 8 19:13:26 PDT 2004


Commit by: donn
Modified files:
chandler/parcels/osaf/contentmodel/ContentModel.py 1.22 1.23
chandler/parcels/osaf/contentmodel/tasks/parcel.xml 1.27 1.28
chandler/parcels/osaf/contentmodel/tests/TestStamping.py 1.1 1.2

Log message:
Updated Stamping for robustness
   * fixed a bug copying collection data during stamping
   * improved robustness and warning messages
   * stamping was broken when _getSuperKinds() was removed, now fixed
   * rewrote the test cases
   * Changed the ContentModel so "requestee" on a Task could be a list

ViewCVS links:
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/contentmodel/ContentModel.py.diff?r1=text&tr1=1.22&r2=text&tr2=1.23
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/contentmodel/tasks/parcel.xml.diff?r1=text&tr1=1.27&r2=text&tr2=1.28
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/contentmodel/tests/TestStamping.py.diff?r1=text&tr1=1.1&r2=text&tr2=1.2

Index: chandler/parcels/osaf/contentmodel/tasks/parcel.xml
diff -u chandler/parcels/osaf/contentmodel/tasks/parcel.xml:1.27 chandler/parcels/osaf/contentmodel/tasks/parcel.xml:1.28
--- chandler/parcels/osaf/contentmodel/tasks/parcel.xml:1.27	Tue Aug  3 09:58:58 2004
+++ chandler/parcels/osaf/contentmodel/tasks/parcel.xml	Sun Aug  8 19:13:24 2004
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="iso-8859-1"?>
 
-<!-- $Revision: 1.27 $ -->
-<!-- $Date: 2004/08/03 16:58:58 $ -->
+<!-- $Revision: 1.28 $ -->
+<!-- $Date: 2004/08/09 02:13:24 $ -->
 <!-- Copyright (c) 2004 Open Source Applications Foundation -->
 <!-- License: http://osafoundation.org/Chandler_0.1_license_terms.htm -->
 
@@ -44,6 +44,7 @@
     <displayName>Requestee</displayName>
     <issues>Type could be Contact, EmailAddress or String</issues>
     <issues>Think about using the icalendar terminology</issues>
+    <cardinality>list</cardinality>
     <type itemref="content:ContentItem"/>
     <inverseAttribute itemref="tasks:taskRequests"/>
   </Attribute>

Index: chandler/parcels/osaf/contentmodel/ContentModel.py
diff -u chandler/parcels/osaf/contentmodel/ContentModel.py:1.22 chandler/parcels/osaf/contentmodel/ContentModel.py:1.23
--- chandler/parcels/osaf/contentmodel/ContentModel.py:1.22	Sat Jul 17 15:22:54 2004
+++ chandler/parcels/osaf/contentmodel/ContentModel.py	Sun Aug  8 19:13:23 2004
@@ -1,14 +1,15 @@
 """ Classes used for contentmodel parcel and kinds.
 """
 
-__revision__  = "$Revision: 1.22 $"
-__date__      = "$Date: 2004/07/17 22:22:54 $"
+__revision__  = "$Revision: 1.23 $"
+__date__      = "$Date: 2004/08/09 02:13:23 $"
 __copyright__ = "Copyright (c) 2003 Open Source Applications Foundation"
 __license__   = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
 from application.Parcel import Parcel
 import repository.item.Item as Item
 import repository.item.Query as Query
+import logging
 
 import application.Globals as Globals
 
@@ -87,14 +88,56 @@
     
     getConversationKind = classmethod(getConversationKind)
 
-class StampError(ValueError):
-    "Can't stamp Item with the requested Mixin Kind"
+class SuperKindSignature(list):
+    """
+    A list of unique superkinds, used as a signature to identify 
+    the structure of a Kind for stamping.
+    The signature is the list of twig node superkinds of the Kind.
+    Using a tree analogy, a twig is the part farthest from the leaf
+    that has no braching.
+    Specifically, a twig node in the SuperKind hierarchy is a node 
+    that has at most one superkind, and whose superkind has at
+    most one superkind, all they way up.
+    The twig superkinds list makes the best signature for two reasons:
+       1) it bypasses all the branching, allowing (A, (B,C)) to match 
+            ((A, B), C)
+       2) it uses the most specialized form when there is no branching,
+            thus if D has superKind B, and B has no superKinds,
+            D is more specialized, so we want to use it.
+    """
+    def __init__(self, aKind, *args, **kwds):
+        """
+        construct with a single Kind
+        """
+        super(SuperKindSignature, self).__init__(*args, **kwds)
+        onTwig = self.appendTwigSuperkinds(aKind)
+        if onTwig:
+            assert len(self) == 0, "Error building superKind Signature"
+            self.append(aKind)
+
+    def appendTwigSuperkinds(self, aKind):
+        """
+        called with a kind, appends all the twig superkinds
+        and returns True iff there's been no branching within
+        this twig
+        """
+        supers = aKind.getAttributeValue('superKinds', default = [])
+        numSupers = len(supers)
+        onTwig = True
+        for kind in supers:
+            onTwig = self.appendTwigSuperkinds(kind)
+            if onTwig and numSupers > 1:
+                self.appendUnique(kind)
+        return numSupers < 2 and onTwig
 
-class KindList(list):
     def appendUnique(self, item):
         if not item in self:
             self.append(item)
 
+    def extend(self, sequence):
+        for item in sequence:
+            self.appendUnique(item)
+    
     def properSubsetOf(self, sequence):
         """
         return True if self is a proper subset of sequence
@@ -105,26 +148,13 @@
                 return False
         return True
 
-    def allLeafSuperKinds(cls, aKind):
-        """
-        Return all the leaf node SuperKinds of a Kind in a list.
-        """
-        def leafNode(kind):
-            supers = kind.getAttributeValue('superKinds', default = None)
-            return supers is None
-        def appendSuperkinds(aKind, supersList):
-            supers = aKind.getAttributeValue('superKinds', default = [])
-            for kind in supers:
-                # all Kinds have Item as their superKind, so the
-                # kinds we want are ones just below the leaf. 
-                if leafNode(kind):
-                    supersList.appendUnique(aKind)
-                appendSuperkinds(kind, supersList)
-        supersList = KindList()
-        appendSuperkinds(aKind, supersList)
-        return supersList
-    allLeafSuperKinds = classmethod(allLeafSuperKinds)
-    
+    def __str__(self):
+        readable = []
+        for item in self:
+            readable.append(item.itsName)
+        theList = ', '.join(readable)
+        return '['+theList+']'
+            
 class ContentItem(Item.Item):
     def __init__(self, name=None, parent=None, kind=None):
         if not parent:
@@ -154,7 +184,55 @@
         self.StampPostProcess(futureKind, dataCarryOver)
         Globals.repository.commit()
 
-    def FindStampedKind(self, operation, mixinKind):
+    def CandidateStampedKinds(self):
+        """
+        return the list of candidate kinds for stamping
+        right now, we consider all kinds
+        """
+        kindKind = Globals.repository.findPath('//Schema/Core/Kind')
+        allKinds = Query.KindQuery().run([kindKind])
+        return allKinds
+
+    def ComputeTargetKindSignature(self, operation, stampKind):
+        """
+        Compute the Kind Signature for stamping.
+        Takes the operation, the kind of self, the stampKind,
+        and computes a target kind signature, which is a list
+        of superKinds.
+        returns a tuple with the signature, and the allowed
+        extra kinds
+        @return: a C{Tuple} (kindSignature, allowedExtra)
+           where kindSignature is a list of kinds, and
+           allowedExtra is an integer telling how many
+           extra kinds are allowed beyond what's in the target.
+        """
+        myKind = self.itsKind
+        soughtSignature = SuperKindSignature(myKind)
+        stampSignature = SuperKindSignature(stampKind)
+        if operation == 'add':
+            for stampSuperKind in stampSignature:
+                if stampSuperKind in soughtSignature:
+                    logging.warning("Trying to stamp with a Kind Signature already present.")
+                    logging.warning("%s has signature %s which overlaps with %s whose signature is %s)" % \
+                                    (stampKind.itsName, stampSignature, \
+                                     myKind.itsName, soughtSignature))
+                    return None # in case this method is overloaded
+            soughtSignature.extend(stampSignature)
+            extrasAllowed = 1
+        else:
+            assert operation == 'remove', "invalid Stamp operation in ContentItem.NewStampedKind: "+operation
+            if not stampSignature.properSubsetOf(soughtSignature):
+                logging.warning("Trying to unstamp with a Kind Signature not already present.")
+                logging.warning("%s has signature %s which is not present in %s: %s" % \
+                                    (stampKind.itsName, stampSignature, \
+                                     myKind.itsName, soughtSignature))
+                return None # in case this method is overloaded
+            for stampSuperKind in stampSignature:
+                soughtSignature.remove(stampSuperKind)
+            extrasAllowed = -1
+        return (soughtSignature, extrasAllowed)
+
+    def FindStampedKind(self, operation, stampKind):
         """
            Return the new Kind that results from self being
         stamped with the Mixin Kind specified.
@@ -166,47 +244,48 @@
         @type mixinKind: C{Kind} of the Mixin
         @return: a C{Kind}
         """
-        myKind = self.itsKind
-        soughtMixins = KindList.allLeafSuperKinds(myKind)
-        if operation == 'add':
-            assert not mixinKind in soughtMixins, "Trying to stamp with a Mixin Kind already present"
-            soughtMixins.append(mixinKind)
-            extrasAllowed = 1
-        else:
-            assert operation == 'remove', "invalid Stamp operation in ContentItem.NewStampedKind: "+operation
-            if not mixinKind in soughtMixins:
-                return None
-            soughtMixins.remove(mixinKind)
-            extrasAllowed = -1
-
-        qualified = []
-        kindKind = Globals.repository.findPath('//Schema/Core/Kind')
-        allKinds = Query.KindQuery().run([kindKind])
-        for candidate in allKinds:
-            superKinds = KindList.allLeafSuperKinds(candidate)
-            extras = len(superKinds) - len(soughtMixins)
+        signature = self.ComputeTargetKindSignature(operation, stampKind)
+        if signature is None:
+            return None
+        soughtSignature, extrasAllowed = signature
+        exactMatches = []
+        closeMatches = []
+        candidates = self.CandidateStampedKinds()
+        for candidate in candidates:
+            candidateSignature = SuperKindSignature(candidate)
+            extras = len(candidateSignature) - len(soughtSignature)
             if extras != 0 and (extras - extrasAllowed) != 0:
                 continue
-            shortList = soughtMixins
-            longList = superKinds
+            shortList = soughtSignature
+            longList = candidateSignature
             if extras < 0:
-                shortList = superKinds
-                longList = soughtMixins
+                shortList = candidateSignature
+                longList = soughtSignature
             if shortList.properSubsetOf(longList):
                 # found a potential match
                 if extras == 0:
                     # exact match
-                    return candidate
+                    exactMatches.append(candidate)
                 else:
                     # close match - keep searching for a better match
-                    qualified.append(candidate)
+                    closeMatches.append(candidate)
 
-        # finished search with no exact matches.  Better have only one candidate.
-        if len(qualified) == 1:
-            return qualified[0]
-        # couldn't find a match, just ReKind with the Mixin Kind
+        # finished search.  Better have only one exact match or else the match is ambiguous.
+        if len(exactMatches) == 1:
+            return exactMatches[0]
+        elif len(exactMatches) == 0:
+            if len(closeMatches) == 1:
+                # zero exact matches is OK when "mixin synergy" is involved.
+                return closeMatches[0]
+
+        # Couldn't find a single exact match or a single close match.
+        logging.warning ("Couldn't find suitable candidates for stamping %s with %s." \
+                        % (self.itsKind.itsName, stampKind.itsName))
+        logging.warning ("Exact matches: %s" % exactMatches)
+        logging.warning ("Close matches: %s" % closeMatches)
+        # ReKind with the Mixin Kind on-the-fly
         return None
-        
+
 
     def AddedRemovedKinds(self, futureKind):
         """
@@ -256,7 +335,13 @@
                 break
             if self.hasAttributeAspect(name, 'redirectTo'):
                 try:
-                    carryOver[name] = self.getAttributeValue(name)
+                    value = self.getAttributeValue(name)
+                    # collections need to deep copy their attribute value
+                    # otherwise there will be two references to the collection,
+                    #  which will go away when the first reference goes away.
+                    value = self.CloneCollectionValue(name, value)
+                    oldRedirect = self.getAttributeAspect(name, 'redirectTo')
+                    carryOver[name] = (value, oldRedirect)
                 except AttributeError:
                     carryOver[name] = None
         return carryOver
@@ -284,8 +369,56 @@
         """
         for key, value in carryOver.items():
             if value is not None:
-                self.setAttributeValue(key, value)
+                value, redirect = value
+                # if the redirect has changed, set the value to the new attribute
+                if self.hasAttributeAspect(key, 'redirectTo'):
+                    newRedirect = self.getAttributeAspect(key, 'redirectTo')
+                    if redirect != newRedirect:
+                        try:
+                            self.setAttributeValue(key, value)
+                        except AttributeError:
+                            pass
     
+    def CloneCollectionValue(self, key, value):
+        """
+        If the value is some kind of collection, we need to make a shallow copy
+        so the collection isn't destroyed when the reference in the other attribute
+        is destroyed.
+        
+        @param key: the name of the indirect attribute.
+        @type name: a string.
+        @param value: the value, already set, in the attribute
+        @type value: anything compatible with the attribute's type
+        
+        I made this a separate method for easy overloading.
+        """
+        # don't need to clone single items
+        if self.getAttributeAspect(key, 'cardinality') == 'single':
+            return value
+
+        # check the first item to see if it has an alias
+        try:
+            alias = value.getAlias(value[0])
+            hasAlias = alias is not None
+        except:
+            hasAlias = False
+
+        # create the clone
+        if hasAlias:
+            clone = {}
+        else:
+            clone = []
+
+        # copy each item, using alias if available
+        for item in value:
+            if hasAlias:
+                alias = value.getAlias(item)
+                clone[alias] = item
+            else:
+                clone.append(item)
+        
+        return clone
+
 class Project(Item.Item):
     def __init__(self, name=None, parent=None, kind=None):
         if not parent:

Index: chandler/parcels/osaf/contentmodel/tests/TestStamping.py
diff -u chandler/parcels/osaf/contentmodel/tests/TestStamping.py:1.1 chandler/parcels/osaf/contentmodel/tests/TestStamping.py:1.2
--- chandler/parcels/osaf/contentmodel/tests/TestStamping.py:1.1	Sat Jul 17 12:26:48 2004
+++ chandler/parcels/osaf/contentmodel/tests/TestStamping.py	Sun Aug  8 19:13:24 2004
@@ -2,8 +2,8 @@
 Unit tests for notes parcel
 """
 
-__revision__  = "$Revision: 1.1 $"
-__date__      = "$Date: 2004/07/17 19:26:48 $"
+__revision__  = "$Revision: 1.2 $"
+__date__      = "$Date: 2004/08/09 02:13:24 $"
 __copyright__ = "Copyright (c) 2003 Open Source Applications Foundation"
 __license__   = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
@@ -17,164 +17,224 @@
 import osaf.contentmodel.calendar.Calendar as Calendar
 import osaf.contentmodel.tests.GenerateItems as GenerateItems
 import mx.DateTime as DateTime
+import logging
 
 from repository.util.Path import Path
 
+verbose = False
+compareWhos = True
+testFailureCases = True
+
+class SavedAttrs:
+    """
+    Saved Attributes stored in one of these
+    """
+    pass
 
 class StampingTest(TestContentModel.ContentModelTestCase):
     """ Test Stamping in the Content Model """
+    def setAttributes(self, item, doWho=True):
+        anAbout = 'aTitleOrHeadline'
+        item.about = anAbout
+        aDate = DateTime.now()
+        item.date = aDate
+        aWhoList = []
+        if doWho:
+            aWhoList.append(GenerateItems.GenerateCalendarParticipant())
+            aWhoList.append(GenerateItems.GenerateCalendarParticipant())
+        if compareWhos:
+            item.who = aWhoList
+
+        savedAttrs = SavedAttrs()
+        try:
+            savedItems = self.savedAttrs
+        except AttributeError:
+            self.savedAttrs = {}
+        self.savedAttrs[item.itsName] = savedAttrs
+
+        savedAttrs.about = anAbout
+        savedAttrs.date = aDate
+        savedAttrs.who = aWhoList
+
+    def assertAttributes(self, item):
+        itemAttrs = self.savedAttrs[item.itsName]
+        self.assertEqual(item.about, itemAttrs.about)
+        self.assertEqual(item.date, itemAttrs.date)
+        # compare the whos
+        if compareWhos:
+            self.assertEqual(len(item.who), len(itemAttrs.who))
+            i = 0
+            for whom in item.who:
+                self.assertEqual(whom, itemAttrs.who[i])
+                i += 1
+
+    def assertKinds(self, item, kindsList):
+        self.assertAttributes(item)
+        for kind in kindsList:
+            self.assert_(item.itsKind.isKindOf(kind))
+
+    def traverseStampSquence(self, item, sequence):
+        for operation, stampKind in sequence:
+            if verbose:
+                message = "stamping %s with %s %s" % \
+                        (item.itsKind.itsName, 
+                         operation,
+                         stampKind.itsName)
+                print message
+                logging.warning(message)
+            item.StampKind(operation, stampKind)
+            self.assertAttributes(item)
+            if operation == 'add':
+                self.assert_(item.itsKind.isKindOf(stampKind))
 
     def testStamping(self):
-        """ Simple test for creating instances of notes and stamping to related kinds """
-
+        # Make sure the contentModel is loaded.
         self.loadParcel("http://osafoundation.org/parcels/osaf/contentmodel")
 
-        # Construct sample items
-        noteItem1 = Notes.Note("noteItem1")
-        noteItem2 = Notes.Note("noteItem2")
-        noteItem3 = Notes.Note("noteItem3")
-
-        # Double check kinds
-        self.assertEqual(noteItem1.itsKind, ContentModel.ContentModel.getNoteKind())
-        self.assertEqual(noteItem2.itsKind, ContentModel.ContentModel.getNoteKind())
-        self.assertEqual(noteItem3.itsKind, ContentModel.ContentModel.getNoteKind())
-
-        # Add some attributes
-        note1About = 'note1About'
-        noteItem1.about = note1About
-        note2About = 'note2About'
-        noteItem2.about = note2About
-        note3About = 'note3About'
-        noteItem3.about = note3About
-        note1Date = DateTime.now()
-        noteItem1.date = note1Date
-        note2Date = DateTime.now()
-        noteItem2.date = note2Date
-        note3Date = DateTime.now()
-        noteItem3.date = note3Date
-        
         # Get the stamp kinds
         mailMixin = Mail.MailParcel.getMailMessageMixinKind()
         taskMixin = Task.TaskParcel.getTaskMixinKind()
         eventMixin = Calendar.CalendarParcel.getCalendarEventMixinKind()
-
-        # Stamp to Mail, Task, Event
-        addOperation = 'add'
-        noteItem1.StampKind(addOperation, mailMixin)
-        noteItem2.StampKind(addOperation, taskMixin)
-        noteItem3.StampKind(addOperation, eventMixin)
-
-        # see that they still have their attributes
-        self.assertEqual(noteItem1.about, note1About)
-        self.assertEqual(noteItem1.date, note1Date)
-        self.assertEqual(noteItem2.about, note2About)
-        self.assertEqual(noteItem2.date, note2Date)
-        self.assertEqual(noteItem3.about, note3About)
-        self.assertEqual(noteItem3.date, note3Date)
-
-        # Stamp additional Task, Event, Mail
-        noteItem1.StampKind(addOperation, taskMixin)
-        noteItem2.StampKind(addOperation, eventMixin)
-        noteItem3.StampKind(addOperation, mailMixin)
-
-        # see that they still have their attributes
-        self.assertEqual(noteItem1.about, note1About)
-        self.assertEqual(noteItem1.date, note1Date)
-        self.assertEqual(noteItem2.about, note2About)
-        self.assertEqual(noteItem2.date, note2Date)
-        self.assertEqual(noteItem3.about, note3About)
-        self.assertEqual(noteItem3.date, note3Date)
-
-        # Stamp additional, so they have all three
-        noteItem1.StampKind(addOperation, eventMixin)
-        noteItem2.StampKind(addOperation, mailMixin)
-        noteItem3.StampKind(addOperation, taskMixin)
-
-        # see that they still have their attributes
-        self.assertEqual(noteItem1.about, note1About)
-        self.assertEqual(noteItem1.date, note1Date)
-        self.assertEqual(noteItem2.about, note2About)
-        self.assertEqual(noteItem2.date, note2Date)
-        self.assertEqual(noteItem3.about, note3About)
-        self.assertEqual(noteItem3.date, note3Date)
-
-        # unstamp in different orders - mail
-        removeOperation = 'remove'
-        noteItem1.StampKind(removeOperation, mailMixin)
-        noteItem2.StampKind(removeOperation, mailMixin)
-        noteItem3.StampKind(removeOperation, mailMixin)
-
-        # see that they still have their attributes
-        self.assertEqual(noteItem1.about, note1About)
-        self.assertEqual(noteItem1.date, note1Date)
-        self.assertEqual(noteItem2.about, note2About)
-        self.assertEqual(noteItem2.date, note2Date)
-        self.assertEqual(noteItem3.about, note3About)
-        self.assertEqual(noteItem3.date, note3Date)
-
-        # unstamp in different orders - event
-        noteItem1.StampKind(removeOperation, eventMixin)
-        noteItem2.StampKind(removeOperation, eventMixin)
-        noteItem3.StampKind(removeOperation, eventMixin)
-
-        # see that they still have their attributes
-        self.assertEqual(noteItem1.about, note1About)
-        self.assertEqual(noteItem1.date, note1Date)
-        self.assertEqual(noteItem2.about, note2About)
-        self.assertEqual(noteItem2.date, note2Date)
-        self.assertEqual(noteItem3.about, note3About)
-        self.assertEqual(noteItem3.date, note3Date)
-
-        # unstamp in different orders - task
-        noteItem1.StampKind(removeOperation, taskMixin)
-        noteItem2.StampKind(removeOperation, taskMixin)
-        noteItem3.StampKind(removeOperation, taskMixin)
-
-        # see that they still have their attributes
-        self.assertEqual(noteItem1.about, note1About)
-        self.assertEqual(noteItem1.date, note1Date)
-        self.assertEqual(noteItem2.about, note2About)
-        self.assertEqual(noteItem2.date, note2Date)
-        self.assertEqual(noteItem3.about, note3About)
-        self.assertEqual(noteItem3.date, note3Date)
-        
-        # should all be back to Note again
-        self.assertEqual(noteItem1.itsKind, ContentModel.ContentModel.getNoteKind())
-        self.assertEqual(noteItem2.itsKind, ContentModel.ContentModel.getNoteKind())
-        self.assertEqual(noteItem3.itsKind, ContentModel.ContentModel.getNoteKind())
+        taskKind = Task.TaskParcel.getTaskKind()
+        mailKind = Mail.MailParcel.getMailMessageKind()
+        eventKind = Calendar.CalendarParcel.getCalendarEventKind()
+        noteKind = ContentModel.ContentModel.getNoteKind()
+
+        # start out with a Note
+        aNote = Notes.Note("noteItem1")
+        self.setAttributes(aNote, doWho=False)
+        self.assertAttributes(aNote)
+        add = 'add'
+        remove = 'remove'
+
+        # stamp everything on and off the note
+        self.traverseStampSquence(aNote, ((add, mailMixin),
+                                          (add, taskMixin),
+                                          (add, eventMixin),
+                                          (remove, eventMixin),
+                                          (remove, taskMixin),
+                                          (remove, mailMixin)))
+
+        # stamp everything on again, remove in a different order
+        self.traverseStampSquence(aNote, ((add, mailMixin),
+                                          (add, taskMixin),
+                                          (add, eventMixin),
+                                          (remove, mailMixin),
+                                          (remove, taskMixin),
+                                          (remove, eventMixin)))
+        self.assertAttributes(aNote)
+
+        # Create a Task, and do all kinds of stamping on it
+        aTask = Task.Task("aTask")
+        self.setAttributes(aTask)
+
+        self.traverseStampSquence(aTask, ((add, eventMixin),
+                                          (remove, taskMixin)))
+        # now it's an Event
+
+        self.traverseStampSquence(aTask, ((add, mailMixin),
+                                          (remove, mailMixin)))
+
+        self.traverseStampSquence(aTask, ((add, mailMixin),
+                                          (add, taskMixin),
+                                          (remove, mailMixin),
+                                          (remove, taskMixin)))
+
+        self.traverseStampSquence(aTask, ((add, taskMixin),
+                                          (add, mailMixin),
+                                          (remove, mailMixin),
+                                          (remove, taskMixin)))
+
+        self.traverseStampSquence(aTask, ((add, mailMixin),
+                                          (remove, eventMixin)))
+        # now it's a Mail
+
+        self.traverseStampSquence(aTask, ((add, taskMixin),
+                                          (remove, mailMixin)))
+        # it's a Task again
+
+        self.traverseStampSquence(aTask, ((add, mailMixin),
+                                          (remove, taskMixin)))
+
+        self.traverseStampSquence(aTask, ((add, taskMixin),
+                                          (remove, mailMixin)))
+        # it's a Task again
+
+        self.traverseStampSquence(aTask, ((add, eventMixin),
+                                          (remove, taskMixin),
+                                          (add, mailMixin),
+                                          (remove, eventMixin),
+                                          (add, taskMixin),
+                                          (remove, mailMixin)))
+        self.assert_(aTask.itsKind.isKindOf(taskKind))
+
+        # check stamping on an Event
+        anEvent = Calendar.CalendarEvent("anEvent")
+        self.setAttributes(anEvent)
+
+        # round-robin it's Kind back to event
+        self.traverseStampSquence(anEvent, ((add, mailMixin),
+                                            (remove, eventMixin),
+                                            (add, taskMixin),
+                                            (remove, mailMixin),
+                                            (add, eventMixin),
+                                            (remove, taskMixin)))
+        self.assert_(anEvent.itsKind.isKindOf(eventKind))
+
+        # check stamping on a Mail Message
+        aMessage = Mail.MailMessage("aMessage")
+        self.setAttributes(aMessage)
+        self.traverseStampSquence(aMessage, ((add, eventMixin),
+                                             (add, taskMixin),
+                                             (remove, eventMixin),
+                                             (remove, taskMixin)))
+        self.assert_(aMessage.itsKind.isKindOf(mailKind))
 
         # now mixin some arbitrary Kind
-        anotherKind = ContentModel.ContentModel.getConversationKind()
+        anotherKind = self.rep.findPath('//parcels/osaf/framework/blocks/Block')
 
         # stamp an event, mail, task with another kind
-        noteItem1.StampKind(addOperation, eventMixin)
-        noteItem2.StampKind(addOperation, mailMixin)
-        noteItem3.StampKind(addOperation, taskMixin)
-
-        noteItem1.StampKind(addOperation, anotherKind)
-        noteItem2.StampKind(addOperation, anotherKind)
-        noteItem3.StampKind(addOperation, anotherKind)
-
-        noteItem1.StampKind(removeOperation, anotherKind)
-        noteItem2.StampKind(removeOperation, anotherKind)
-        noteItem3.StampKind(removeOperation, anotherKind)
-
-        noteItem1.StampKind(removeOperation, eventMixin)
-        noteItem2.StampKind(removeOperation, mailMixin)
-        noteItem3.StampKind(removeOperation, taskMixin)
+        aNote.StampKind(add, anotherKind)
+        aTask.StampKind(add, anotherKind)
+        anEvent.StampKind(add, anotherKind)
+        aMessage.StampKind(add, anotherKind)
+
+        self.assertKinds(aNote, (noteKind, anotherKind))
+        self.assertKinds(aTask, (taskKind, anotherKind))
+        self.assertKinds(anEvent, (eventKind, anotherKind))
+        self.assertKinds(aMessage, (mailKind, anotherKind))
+
+        # unstamp with another kind
+        aNote.StampKind(remove, anotherKind)
+        aTask.StampKind(remove, anotherKind)
+        anEvent.StampKind(remove, anotherKind)
+        aMessage.StampKind(remove, anotherKind)
+
+        # see that they still have their attributes
+        self.assertKinds(aNote, (noteKind, ))
+        self.assertKinds(aTask, (taskKind, ))
+        self.assertKinds(anEvent, (eventKind, ))
+        self.assertKinds(aMessage, (mailKind, ))
+
+        # Test some failure cases
+        # These cases should produce suitable warning messages in Chandler.log
+        if testFailureCases:
+            anotherEvent = Calendar.CalendarEvent("anotherEvent")
+            self.setAttributes(anotherEvent)
+            self.assert_(anotherEvent.itsKind.isKindOf(eventKind))
+            try:
+                # double stamping
+                self.traverseStampSquence(anotherEvent, ((add, mailMixin),
+                                                         (add, mailMixin)))
+            except:
+                pass
+
+            try:
+                # unstamping something not present
+                self.traverseStampSquence(anotherEvent, ((remove, taskMixin), ))
+            except:
+                pass
 
-        # see that they still have their attributes
-        self.assertEqual(noteItem1.about, note1About)
-        self.assertEqual(noteItem1.date, note1Date)
-        self.assertEqual(noteItem2.about, note2About)
-        self.assertEqual(noteItem2.date, note2Date)
-        self.assertEqual(noteItem3.about, note3About)
-        self.assertEqual(noteItem3.date, note3Date)
-        
-        # should all be back to Note again
-        self.assertEqual(noteItem1.itsKind, ContentModel.ContentModel.getNoteKind())
-        self.assertEqual(noteItem2.itsKind, ContentModel.ContentModel.getNoteKind())
-        self.assertEqual(noteItem3.itsKind, ContentModel.ContentModel.getNoteKind())
 
 if __name__ == "__main__":
     unittest.main()



More information about the Commits mailing list