[Checkins] SVN: z3c.vcsync/trunk/src/z3c/vcsync/ Rename README.txt to internal.txt.

Martijn Faassen faassen at infrae.com
Mon Apr 21 15:07:30 EDT 2008


Log message for revision 85569:
  Rename README.txt to internal.txt.
  

Changed:
  D   z3c.vcsync/trunk/src/z3c/vcsync/README.txt
  A   z3c.vcsync/trunk/src/z3c/vcsync/internal.txt
  U   z3c.vcsync/trunk/src/z3c/vcsync/tests.py

-=-
Deleted: z3c.vcsync/trunk/src/z3c/vcsync/README.txt
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/README.txt	2008-04-21 19:06:11 UTC (rev 85568)
+++ z3c.vcsync/trunk/src/z3c/vcsync/README.txt	2008-04-21 19:07:29 UTC (rev 85569)
@@ -1,740 +0,0 @@
-Internal tests
-==============
-
-This document contains a number of internal tests of the
-synchronization facility.
-
-To start
---------
-
-Let's first grok this package::
-
-  >>> import grok.testing
-  >>> grok.testing.grok('z3c.vcsync')
-
-Serialization
--------------
-
-In order to export content to a version control system, it first needs
-to be possible to serialize a content object to a text representation.
-
-For the purposes of this document, we have defined a simple item that
-just carries an integer payload attribute::
-
-  >>> class Item(object):
-  ...   def __init__(self, payload):
-  ...     self.payload = payload
-  >>> item = Item(payload=1)
-  >>> item.payload
-  1
-
-We will use an ISerializer adapter to serialize it to a file. Let's
-define the adapter::
-
-  >>> from z3c.vcsync.interfaces import ISerializer
-  >>> class ItemSerializer(grok.Adapter):
-  ...     grok.provides(ISerializer)
-  ...     grok.context(Item)
-  ...     def serialize(self, f):
-  ...         f.write(str(self.context.payload))
-  ...         f.write('\n')
-  ...     def name(self):
-  ...         return self.context.__name__ + '.test'
-
-Let's test our adapter::
-
-  >>> from StringIO import StringIO
-  >>> f= StringIO()
-  >>> ItemSerializer(item).serialize(f)
-  >>> f.getvalue()
-  '1\n'
-
-Let's register the adapter::
-
-  >>> grok.testing.grok_component('ItemSerializer', ItemSerializer)
-  True
-
-We can now use the adapter::
-
-  >>> f = StringIO()
-  >>> ISerializer(item).serialize(f)
-  >>> f.getvalue()
-  '1\n'
-
-Export persistent state to version control system checkout
-----------------------------------------------------------
-
-Let's imagine we have this object structure consisting of a container
-with some items and sub-containers in it::
-
-  >>> data = Container()
-  >>> data.__name__ = 'root'
-  >>> data['foo'] = Item(payload=1)
-  >>> data['bar'] = Item(payload=2)
-  >>> data['sub'] = Container()
-  >>> data['sub']['qux'] = Item(payload=3)
-
-This object structure has some test payload data::
-
-  >>> data['foo'].payload
-  1
-  >>> data['sub']['qux'].payload
-  3
-
-We have a checkout in testpath on the filesystem::
-
-  >>> testpath = create_test_dir()
-  >>> checkout = TestCheckout(testpath)
-
-We also have a test state representing the object data::
-
-  >>> state = TestState(data)
-
-The test state will always return a list of all objects. We pass in
-``None`` for the revision_nr here, as the TestState ignores this
-information anyway::
-
-  >>> sorted([obj.__name__ for obj in state.objects(None)])
-  ['bar', 'foo', 'qux', 'root', 'sub']
-
-Now let's synchronize. For this, we need a synchronizer initialized
-with the checkout and the state::
-  
-  >>> from z3c.vcsync import Synchronizer
-  >>> s = Synchronizer(checkout, state)
-
-We now save the state into that checkout. We are passing ``None`` for
-the revision_nr for the time being::
-
-  >>> s.save(None)
-
-The filesystem should now contain the right objects. Everything is
-always saved in a directory called ``root``:
- 
-  >>> root = testpath.join('root')
-  >>> root.check(dir=True)
-  True
-
-This root directory should contain the right objects::
-
-  >>> sorted([entry.basename for entry in root.listdir()])
-  ['bar.test', 'foo.test', 'sub']
-
-We expect the right contents in ``bar.test`` and ``foo.test``::
-
-  >>> root.join('bar.test').read()
-  '2\n'
-  >>> root.join('foo.test').read()
-  '1\n'
-
-``sub`` is a container so should be represented as a directory::
-
-  >>> sub_path = root.join('sub')
-  >>> sub_path.check(dir=True)
-  True
-
-  >>> sorted([entry.basename for entry in sub_path.listdir()])
-  ['qux.test']
-
-  >>> sub_path.join('qux.test').read()
-  '3\n'
-
-Modifying an existing checkout
-------------------------------
-
-We will now change some data in the ZODB again.
-
-Let's add ``hoi``::
-  
-  >>> data['hoi'] = Item(payload=4)
-
-And let's delete ``bar``::
-
-  >>> del data['bar']
-
-Since we are removing something, we need inform the state about it. We
-do this manually here, though in a real application typically you
-would subscribe to the ``IObjectRemovedEvent``.
-
-  >>> removed_paths = ['/root/bar']
-  >>> state.removed_paths = removed_paths
-
-The added object always will return with ``objects``, but in your
-application you may also need to let the state know.
- 
-Let's save the object structure again to the same checkout::
- 
-  >>> s.save(None)
-
-We expect the ``hoi.test`` file to be added::
-
-  >>> root.join('hoi.test').read()
-  '4\n'
-
-We also expect the ``bar.test`` file to be removed::
-
-  >>> root.join('bar.test').check()
-  False
-
-Modifying an existing checkout, some edge cases
------------------------------------------------
-
-The ZODB has changed again.  Item 'hoi' has changed from an item into
-a container::
-
-  >>> del data['hoi']
-  >>> data['hoi'] = Container()
-
-Let's create a new removed list. The item 'hoi' was removed before it
-was removed with a new container with the same name, so we have to
-remember this::
-
-  >>> removed_paths = ['/root/hoi']
-  >>> state.removed_paths = removed_paths
-
-We put some things into the new container::
-
-  >>> data['hoi']['something'] = Item(payload=15)
-
-We export again into the existing checkout (which still has 'hoi' as a
-file)::
-
-  >>> s.save(None)
-
-Let's check the filesystem state::
-
-  >>> sorted([entry.basename for entry in root.listdir()])
-  ['foo.test', 'hoi', 'sub']
-
-We expect ``hoi`` to contain ``something.test``::
-
-  >>> hoi_path = root.join('hoi')
-  >>> something_path = hoi_path.join('something.test')
-  >>> something_path.read()
-  '15\n'
-
-Let's now change the ZODB again and change the ``hoi`` container back
-into a file::
-
-  >>> del data['hoi']
-  >>> data['hoi'] = Item(payload=16)
-  >>> s.save(None)
-
-This means we need to mark the path to the container to be removed::
-
-  >>> removed_paths = ['/root/hoi']
-  >>> state.removed_paths = removed_paths
-
-We expect to see a ``hoi.test`` but no ``hoi`` directory anymore::
-
-  >>> sorted([entry.basename for entry in root.listdir()])
-  ['foo.test', 'hoi.test', 'sub']
-
-Note: creating a container with the name ``hoi.test`` (using the
-``.test`` postfix) will lead to trouble now, as we already have a file
-``hoi.test``. ``svn`` doesn't allow a single-step replace of a file
-with a directory - as expressed earlier, an ``svn up`` would need to
-be issued first, but this would be too early in the process. Solving
-this problem is quite involved. Instead, we require the application to
-avoid creating any directories with a postfix in use by items. The
-following should be forbidden::
-
-  data['hoi.test'] = Container()
-
-multiple object types
----------------------
-
-We will now introduce a second object type::
-
-  >>> class OtherItem(object):
-  ...   def __init__(self, payload):
-  ...     self.payload = payload
-
-We will need an ``ISerializer`` adapter for ``OtherItem`` too::
-
-  >>> class OtherItemSerializer(grok.Adapter):
-  ...     grok.provides(ISerializer)
-  ...     grok.context(OtherItem)
-  ...     def serialize(self, f):
-  ...         f.write(str(self.context.payload))
-  ...         f.write('\n')
-  ...     def name(self):
-  ...         return self.context.__name__ + '.other'
-  >>> grok.testing.grok_component('OtherItemSerializer', OtherItemSerializer)
-  True
-
-Note that the extension we serialize to is ``.other``.
-
-Let's now change the ``hoi`` object into an ``OtherItem``. First we remove
-the original ``hoi``::
-
-  >>> del data['hoi']
-
-We need to mark this removal in our ``removed_paths`` list::
-
-  >>> state.removed_paths = ['/root/hoi']
-
-We then introduce the new ``hoi``::
-
-  >>> data['hoi'] = OtherItem(23)
-
-Let's serialize::
-
-  >>> s.save(None)
-
-We expect to see a ``hoi.other`` item now::
-
-  >>> sorted([entry.basename for entry in root.listdir()])
-  ['foo.test', 'hoi.other', 'sub']
-
-Let's change the object back again::
-
-  >>> del data['hoi']
-  >>> state.removed_paths = ['/root/hoi']
-  >>> data['hoi'] = Item(payload=16)
-  >>> s.save(None)
-
-We expect to see a ``hoi.test`` item again::
-
-  >>> sorted([entry.basename for entry in root.listdir()])
-  ['foo.test', 'hoi.test', 'sub']
-
-loading a checkout state into python objects
---------------------------------------------
-
-Let's load the current filesystem layout into python
-objects. Factories are registered as utilities for the different
-things we can encounter on the filesystem. Let's look at items
-first. A ``IParser`` utility is registered for the ``.test``
-extension::
-
-  >>> from z3c.vcsync.interfaces import IParser
-  >>> class ItemParser(grok.GlobalUtility):
-  ...   grok.provides(IParser)
-  ...   grok.name('.test')
-  ...   def __call__(self, object, path):
-  ...      object.payload = int(path.read())
-  >>> grok.testing.grok_component('ItemParser', ItemParser)
-  True
- 
-To have the ability to create new objects, a factory is registered for
-the ``.test`` extension as well, implemented in terms of ``ItemParser``::
-
-  >>> from z3c.vcsync.interfaces import IVcFactory
-  >>> from zope import component
-  >>> class ItemFactory(grok.GlobalUtility):
-  ...   grok.provides(IVcFactory)
-  ...   grok.name('.test')
-  ...   def __call__(self, path):
-  ...       parser = component.getUtility(IParser, '.test')
-  ...       item = Item(None) # dummy payload
-  ...       parser(item, path)
-  ...       return item
-  >>> grok.testing.grok_component('ItemFactory', ItemFactory)
-  True
-
-Now for containers. They are registered for an empty extension::
-
-  >>> class ContainerParser(grok.GlobalUtility):
-  ...   grok.provides(IParser)
-  ...   def __call__(self, object, path):
-  ...       pass # do nothing with existing containers
-  >>> grok.testing.grok_component('ContainerParser', ContainerParser)
-  True
-
-We implement ``ContainerFactory`` in terms of
-``ContainerParser``. Note that because ``ContainerParser`` doesn't
-actually do something in this example, we could optimize it and remove
-the use of ``IParser`` here, but we won't do this for consistency::
-
-  >>> class ContainerFactory(grok.GlobalUtility):
-  ...   grok.provides(IVcFactory)
-  ...   def __call__(self, path):
-  ...       parser = component.getUtility(IParser, '')
-  ...       container = Container()
-  ...       parser(container, path)
-  ...       return container
-  >>> grok.testing.grok_component('ContainerFactory', ContainerFactory)
-  True
-
-We need to maintain a list of everything modified or added, and a list
-of everything deleted by the update operation. Normally this
-information is extracted from the version control system, but for the
-purposes of this test we maintain it manually. In this case,
-everything is added so appears in the files list::
-
-  >>> checkout._files = [root.join('foo.test'), root.join('hoi.test'),
-  ...   root.join('sub'), root.join('sub', 'qux.test')]
-
-Nothing was removed::
-
-  >>> checkout._removed = []
-
-Let's load up the contents from the filesystem now, into a new container::
-
-  >>> container2 = Container()
-  >>> container2.__name__ = 'root'
-
-In order to load into a different container, we need to set up a new
-synchronizer with a new state::
-
-  >>> s = Synchronizer(checkout, TestState(container2))
-
-We can now do the loading::
-
-  >>> dummy = s.load(None)
-
-We expect the proper objects to be in the new container::
-
-  >>> sorted(container2.keys())
-  ['foo', 'hoi', 'sub']
-
-We check whether the items contains the right information::
-
-  >>> isinstance(container2['foo'], Item)
-  True
-  >>> container2['foo'].payload
-  1
-  >>> isinstance(container2['hoi'], Item)
-  True
-  >>> container2['hoi'].payload
-  16
-  >>> isinstance(container2['sub'], Container)
-  True
-  >>> sorted(container2['sub'].keys())
-  ['qux']
-  >>> container2['sub']['qux'].payload
-  3
-
-version control changes a file
-------------------------------
-
-Now we synchronize our checkout by synchronizing the checkout with the
-central coordinating server (or shared branch in case of a distributed
-version control system). We do a ``checkout.up()`` that causes the
-text in a file to be modified.
-
-The special checkout class we use for example purposes will call
-``update_function`` during an update. This function should then
-simulate what might happen during a version control system ``update``
-operation. Let's define one here that modifies text in a file::
-
-  >>> hoi_path = root.join('hoi.test')
-  >>> def update_function():
-  ...    hoi_path.write('200\n')
-  >>> checkout.update_function = update_function
-
-Now let's do an update::
-
-  >>> checkout.up()
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = [hoi_path]
-  >>> checkout._removed = []
-
-We will reload the checkout into Python objects::
-
-  >>> dummy = s.load(None)
- 
-We expect the ``hoi`` object to be modified::
-
-  >>> container2['hoi'].payload
-  200
-
-version control adds a file
----------------------------
-
-We update our checkout again and cause a file to be added::
-
-  >>> hallo = root.join('hallo.test').ensure()
-  >>> def update_function():
-  ...   hallo.write('300\n')
-  >>> checkout.update_function = update_function
-
-  >>> checkout.up()
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = [hallo]
-  >>> checkout._removed = []
-
-We will reload the checkout into Python objects again::
-
-  >>> dummy = s.load(None)
- 
-We expect there to be a new object ``hallo``::
-
-  >>> 'hallo' in container2.keys()
-  True
-
-version control removes a file
-------------------------------
-
-We update our checkout and cause a file to be removed::
-
-  >>> def update_function():
-  ...   root.join('hallo.test').remove()
-  >>> checkout.update_function = update_function
-
-  >>> checkout.up()
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = []
-  >>> checkout._removed = [hallo]
-
-We will reload the checkout into Python objects::
-  
-  >>> dummy = s.load(None)
-
-We expect the object ``hallo`` to be gone again::
-
-  >>> 'hallo' in container2.keys()
-  False
-
-version control adds a directory
---------------------------------
-
-We update our checkout and cause a directory (with a file inside) to be
-added::
-
-  >>> newdir_path = root.join('newdir')
-  >>> def update_function():
-  ...   newdir_path.ensure(dir=True)
-  ...   newfile_path = newdir_path.join('newfile.test').ensure()
-  ...   newfile_path.write('400\n')
-  >>> checkout.update_function = update_function
-  
-  >>> checkout.up()
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = [newdir_path, newdir_path.join('newfile.test')]
-  >>> checkout._removed = []
-
-Reloading this will cause a new container to exist::
-
-  >>> dummy = s.load(None)
-  >>> 'newdir' in container2.keys()
-  True
-  >>> isinstance(container2['newdir'], Container)
-  True
-  >>> container2['newdir']['newfile'].payload
-  400
-
-version control removes a directory
------------------------------------
-
-We update our checkout once again and cause a directory to be removed::
-
-  >>> def update_function():
-  ...   newdir_path.remove()
-  >>> checkout.update_function = update_function
-
-  >>> checkout.up()
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = []
-  >>> checkout._removed = [newdir_path, newdir_path.join('newfile.test')]
-
-And reload the data::
-
-  >>> dummy = s.load(None)
-
-Reloading this will cause the new container to be gone again::
-
-  >>> 'newdir' in container2.keys()
-  False
-
-version control changes a file into a directory
------------------------------------------------
-
-Some sequence of actions by other users has caused a name that previously
-referred to a file to now refer to a directory::
-
-  >>> hoi_path2 = root.join('hoi')
-  >>> def update_function():
-  ...   hoi_path.remove()
-  ...   hoi_path2.ensure(dir=True)
-  ...   some_path = hoi_path2.join('some.test').ensure(file=True)
-  ...   some_path.write('1000\n')
-  >>> checkout.update_function = update_function
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = [hoi_path2, hoi_path2.join('some.test')]
-  >>> checkout._removed = [hoi_path]
-
-  >>> checkout.up()
-
-Reloading this will cause a new container to be there instead of the file::
-
-  >>> dummy = s.load(None)
-  >>> isinstance(container2['hoi'], Container)
-  True
-  >>> container2['hoi']['some'].payload
-  1000
-
-version control changes a directory into a file
------------------------------------------------
-
-Some sequence of actions by other users has caused a name that
-previously referred to a directory to now refer to a file::
-
-  >>> def update_function():
-  ...   hoi_path2.remove()
-  ...   hoi_path = root.join('hoi.test').ensure()
-  ...   hoi_path.write('2000\n')
-  >>> checkout.update_function = update_function
-
-  >>> checkout.up()
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = [hoi_path]
-  >>> checkout._removed = [hoi_path2.join('some.test'), hoi_path2]
-
-Reloading this will cause a new item to be there instead of the
-container::
-
-  >>> dummy = s.load(None)
-  >>> isinstance(container2['hoi'], Item)
-  True
-  >>> container2['hoi'].payload
-  2000
-
-version control changes a file into one with a different file type
-------------------------------------------------------------------
-
-Some sequence of actions by other users has ccaused a name that
-previously referred to one type of object to now refer to another kind.
-Let's define an ``Item2``::
-
-  >>> class Item2(object):
-  ...   def __init__(self, payload):
-  ...     self.payload = payload
-
-And a parser and factory for it::
-  
-  >>> class Item2Parser(grok.GlobalUtility):
-  ...   grok.provides(IParser)
-  ...   grok.name('.test2')
-  ...   def __call__(self, object, path):
-  ...      object.payload = int(path.read()) ** 2
-  >>> grok.testing.grok_component('Item2Parser', Item2Parser)
-  True 
-  >>> class Item2Factory(grok.GlobalUtility):
-  ...   grok.provides(IVcFactory)
-  ...   grok.name('.test2')
-  ...   def __call__(self, path):
-  ...       parser = component.getUtility(IParser, '.test2')
-  ...       item = Item2(None) # dummy payload
-  ...       parser(item, path)
-  ...       return item
-  >>> grok.testing.grok_component('Item2Factory', Item2Factory)
-  True
-
-Now we define an update function that replaces ``hoi.test`` with
-``hoi.test2``::
-
-  >>> hoi_path3 = root.join('hoi.test2')
-  >>> def update_function():
-  ...    hoi_path.remove()
-  ...    hoi_path3.ensure()
-  ...    hoi_path3.write('44\n')
-  >>> checkout.update_function = update_function
-  >>> checkout.up()
-
-We maintain the list of things changed::
-
-  >>> checkout._files = [hoi_path3]
-  >>> checkout._removed = [hoi_path]
-
-Reloading this will cause a new type of item to be there instead of the old
-type::
-
-  >>> dummy = s.load(None)
-  >>> isinstance(container2['hoi'], Item2)
-  True
-  >>> container2['hoi'].payload
-  1936
-
-Let's restore the original ``hoi.test`` object::
- 
-  >>> hoi_path3.remove()
-  >>> hoi_path.write('2000\n')
-  >>> del container2['hoi']
-  >>> container2['hoi'] = Item(2000)
-
-Complete synchronization
-------------------------
-
-Let's now exercise the ``sync`` method directly. First we'll modify
-the payload of the ``hoi`` item::
-
-  >>> container2['hoi'].payload = 3000
- 
-Next, we willl add a new ``alpha`` file to the checkout when we do an
-``up()``, so again we simulate the actions of our version control system::
-
-  >>> alpha_path = root.join('alpha.test').ensure()
-  >>> def update_function():
-  ...   alpha_path.write('4000\n')
-  >>> checkout.update_function = update_function
-
-We maintain the lists of things changed::
-
-  >>> checkout._files = [alpha_path]
-  >>> checkout._removed = []
-
-The revision number before full synchronization::
-
-  >>> checkout.revision_nr()
-  8
-
-Now we'll synchronize with the memory structure. We'll pass a special
-function along that prints out all objects that have been created or
-modified::
-
-  >>> def f(obj):
-  ...   print "modified:", obj.__name__
-  >>> info = s.sync(None, message='', modified_function=f)
-  modified: alpha
-  >>> info.revision_nr
-  9
-
-We can get a report of what happened. No files were removed::
-
-  >>> info.files_removed()
-  []
-
-One file, alpha, was added to the checkout during our update (by
-someone else)::
-
-  >>> info.files_changed()
-  [local('.../root/alpha.test')]
-
-We removed no objects from our database since the last update::
-
-  >>> info.objects_removed()
-  []
-
-We did change one object, 'hoi', but the test infrastructure always returns
-all objects here (returning more objects is allowed)::
-
-  >>> info.objects_changed()
-  ['/root/foo', '/root/hoi', '/root', '/root/sub/qux', '/root/sub']
-
-We expect the checkout to reflect the changed state of the ``hoi`` object::
-
-  >>> root.join('hoi.test').read()
-  '3000\n'
-
-We also expect the database to reflect the creation of the new
-``alpha`` object::
-
-  >>> container2['alpha'].payload
-  4000
-

Copied: z3c.vcsync/trunk/src/z3c/vcsync/internal.txt (from rev 85568, z3c.vcsync/trunk/src/z3c/vcsync/README.txt)
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/internal.txt	                        (rev 0)
+++ z3c.vcsync/trunk/src/z3c/vcsync/internal.txt	2008-04-21 19:07:29 UTC (rev 85569)
@@ -0,0 +1,740 @@
+Internal tests
+==============
+
+This document contains a number of internal tests of the
+synchronization facility.
+
+To start
+--------
+
+Let's first grok this package::
+
+  >>> import grok.testing
+  >>> grok.testing.grok('z3c.vcsync')
+
+Serialization
+-------------
+
+In order to export content to a version control system, it first needs
+to be possible to serialize a content object to a text representation.
+
+For the purposes of this document, we have defined a simple item that
+just carries an integer payload attribute::
+
+  >>> class Item(object):
+  ...   def __init__(self, payload):
+  ...     self.payload = payload
+  >>> item = Item(payload=1)
+  >>> item.payload
+  1
+
+We will use an ISerializer adapter to serialize it to a file. Let's
+define the adapter::
+
+  >>> from z3c.vcsync.interfaces import ISerializer
+  >>> class ItemSerializer(grok.Adapter):
+  ...     grok.provides(ISerializer)
+  ...     grok.context(Item)
+  ...     def serialize(self, f):
+  ...         f.write(str(self.context.payload))
+  ...         f.write('\n')
+  ...     def name(self):
+  ...         return self.context.__name__ + '.test'
+
+Let's test our adapter::
+
+  >>> from StringIO import StringIO
+  >>> f= StringIO()
+  >>> ItemSerializer(item).serialize(f)
+  >>> f.getvalue()
+  '1\n'
+
+Let's register the adapter::
+
+  >>> grok.testing.grok_component('ItemSerializer', ItemSerializer)
+  True
+
+We can now use the adapter::
+
+  >>> f = StringIO()
+  >>> ISerializer(item).serialize(f)
+  >>> f.getvalue()
+  '1\n'
+
+Export persistent state to version control system checkout
+----------------------------------------------------------
+
+Let's imagine we have this object structure consisting of a container
+with some items and sub-containers in it::
+
+  >>> data = Container()
+  >>> data.__name__ = 'root'
+  >>> data['foo'] = Item(payload=1)
+  >>> data['bar'] = Item(payload=2)
+  >>> data['sub'] = Container()
+  >>> data['sub']['qux'] = Item(payload=3)
+
+This object structure has some test payload data::
+
+  >>> data['foo'].payload
+  1
+  >>> data['sub']['qux'].payload
+  3
+
+We have a checkout in testpath on the filesystem::
+
+  >>> testpath = create_test_dir()
+  >>> checkout = TestCheckout(testpath)
+
+We also have a test state representing the object data::
+
+  >>> state = TestState(data)
+
+The test state will always return a list of all objects. We pass in
+``None`` for the revision_nr here, as the TestState ignores this
+information anyway::
+
+  >>> sorted([obj.__name__ for obj in state.objects(None)])
+  ['bar', 'foo', 'qux', 'root', 'sub']
+
+Now let's synchronize. For this, we need a synchronizer initialized
+with the checkout and the state::
+  
+  >>> from z3c.vcsync import Synchronizer
+  >>> s = Synchronizer(checkout, state)
+
+We now save the state into that checkout. We are passing ``None`` for
+the revision_nr for the time being::
+
+  >>> s.save(None)
+
+The filesystem should now contain the right objects. Everything is
+always saved in a directory called ``root``:
+ 
+  >>> root = testpath.join('root')
+  >>> root.check(dir=True)
+  True
+
+This root directory should contain the right objects::
+
+  >>> sorted([entry.basename for entry in root.listdir()])
+  ['bar.test', 'foo.test', 'sub']
+
+We expect the right contents in ``bar.test`` and ``foo.test``::
+
+  >>> root.join('bar.test').read()
+  '2\n'
+  >>> root.join('foo.test').read()
+  '1\n'
+
+``sub`` is a container so should be represented as a directory::
+
+  >>> sub_path = root.join('sub')
+  >>> sub_path.check(dir=True)
+  True
+
+  >>> sorted([entry.basename for entry in sub_path.listdir()])
+  ['qux.test']
+
+  >>> sub_path.join('qux.test').read()
+  '3\n'
+
+Modifying an existing checkout
+------------------------------
+
+We will now change some data in the ZODB again.
+
+Let's add ``hoi``::
+  
+  >>> data['hoi'] = Item(payload=4)
+
+And let's delete ``bar``::
+
+  >>> del data['bar']
+
+Since we are removing something, we need inform the state about it. We
+do this manually here, though in a real application typically you
+would subscribe to the ``IObjectRemovedEvent``.
+
+  >>> removed_paths = ['/root/bar']
+  >>> state.removed_paths = removed_paths
+
+The added object always will return with ``objects``, but in your
+application you may also need to let the state know.
+ 
+Let's save the object structure again to the same checkout::
+ 
+  >>> s.save(None)
+
+We expect the ``hoi.test`` file to be added::
+
+  >>> root.join('hoi.test').read()
+  '4\n'
+
+We also expect the ``bar.test`` file to be removed::
+
+  >>> root.join('bar.test').check()
+  False
+
+Modifying an existing checkout, some edge cases
+-----------------------------------------------
+
+The ZODB has changed again.  Item 'hoi' has changed from an item into
+a container::
+
+  >>> del data['hoi']
+  >>> data['hoi'] = Container()
+
+Let's create a new removed list. The item 'hoi' was removed before it
+was removed with a new container with the same name, so we have to
+remember this::
+
+  >>> removed_paths = ['/root/hoi']
+  >>> state.removed_paths = removed_paths
+
+We put some things into the new container::
+
+  >>> data['hoi']['something'] = Item(payload=15)
+
+We export again into the existing checkout (which still has 'hoi' as a
+file)::
+
+  >>> s.save(None)
+
+Let's check the filesystem state::
+
+  >>> sorted([entry.basename for entry in root.listdir()])
+  ['foo.test', 'hoi', 'sub']
+
+We expect ``hoi`` to contain ``something.test``::
+
+  >>> hoi_path = root.join('hoi')
+  >>> something_path = hoi_path.join('something.test')
+  >>> something_path.read()
+  '15\n'
+
+Let's now change the ZODB again and change the ``hoi`` container back
+into a file::
+
+  >>> del data['hoi']
+  >>> data['hoi'] = Item(payload=16)
+  >>> s.save(None)
+
+This means we need to mark the path to the container to be removed::
+
+  >>> removed_paths = ['/root/hoi']
+  >>> state.removed_paths = removed_paths
+
+We expect to see a ``hoi.test`` but no ``hoi`` directory anymore::
+
+  >>> sorted([entry.basename for entry in root.listdir()])
+  ['foo.test', 'hoi.test', 'sub']
+
+Note: creating a container with the name ``hoi.test`` (using the
+``.test`` postfix) will lead to trouble now, as we already have a file
+``hoi.test``. ``svn`` doesn't allow a single-step replace of a file
+with a directory - as expressed earlier, an ``svn up`` would need to
+be issued first, but this would be too early in the process. Solving
+this problem is quite involved. Instead, we require the application to
+avoid creating any directories with a postfix in use by items. The
+following should be forbidden::
+
+  data['hoi.test'] = Container()
+
+multiple object types
+---------------------
+
+We will now introduce a second object type::
+
+  >>> class OtherItem(object):
+  ...   def __init__(self, payload):
+  ...     self.payload = payload
+
+We will need an ``ISerializer`` adapter for ``OtherItem`` too::
+
+  >>> class OtherItemSerializer(grok.Adapter):
+  ...     grok.provides(ISerializer)
+  ...     grok.context(OtherItem)
+  ...     def serialize(self, f):
+  ...         f.write(str(self.context.payload))
+  ...         f.write('\n')
+  ...     def name(self):
+  ...         return self.context.__name__ + '.other'
+  >>> grok.testing.grok_component('OtherItemSerializer', OtherItemSerializer)
+  True
+
+Note that the extension we serialize to is ``.other``.
+
+Let's now change the ``hoi`` object into an ``OtherItem``. First we remove
+the original ``hoi``::
+
+  >>> del data['hoi']
+
+We need to mark this removal in our ``removed_paths`` list::
+
+  >>> state.removed_paths = ['/root/hoi']
+
+We then introduce the new ``hoi``::
+
+  >>> data['hoi'] = OtherItem(23)
+
+Let's serialize::
+
+  >>> s.save(None)
+
+We expect to see a ``hoi.other`` item now::
+
+  >>> sorted([entry.basename for entry in root.listdir()])
+  ['foo.test', 'hoi.other', 'sub']
+
+Let's change the object back again::
+
+  >>> del data['hoi']
+  >>> state.removed_paths = ['/root/hoi']
+  >>> data['hoi'] = Item(payload=16)
+  >>> s.save(None)
+
+We expect to see a ``hoi.test`` item again::
+
+  >>> sorted([entry.basename for entry in root.listdir()])
+  ['foo.test', 'hoi.test', 'sub']
+
+loading a checkout state into python objects
+--------------------------------------------
+
+Let's load the current filesystem layout into python
+objects. Factories are registered as utilities for the different
+things we can encounter on the filesystem. Let's look at items
+first. A ``IParser`` utility is registered for the ``.test``
+extension::
+
+  >>> from z3c.vcsync.interfaces import IParser
+  >>> class ItemParser(grok.GlobalUtility):
+  ...   grok.provides(IParser)
+  ...   grok.name('.test')
+  ...   def __call__(self, object, path):
+  ...      object.payload = int(path.read())
+  >>> grok.testing.grok_component('ItemParser', ItemParser)
+  True
+ 
+To have the ability to create new objects, a factory is registered for
+the ``.test`` extension as well, implemented in terms of ``ItemParser``::
+
+  >>> from z3c.vcsync.interfaces import IVcFactory
+  >>> from zope import component
+  >>> class ItemFactory(grok.GlobalUtility):
+  ...   grok.provides(IVcFactory)
+  ...   grok.name('.test')
+  ...   def __call__(self, path):
+  ...       parser = component.getUtility(IParser, '.test')
+  ...       item = Item(None) # dummy payload
+  ...       parser(item, path)
+  ...       return item
+  >>> grok.testing.grok_component('ItemFactory', ItemFactory)
+  True
+
+Now for containers. They are registered for an empty extension::
+
+  >>> class ContainerParser(grok.GlobalUtility):
+  ...   grok.provides(IParser)
+  ...   def __call__(self, object, path):
+  ...       pass # do nothing with existing containers
+  >>> grok.testing.grok_component('ContainerParser', ContainerParser)
+  True
+
+We implement ``ContainerFactory`` in terms of
+``ContainerParser``. Note that because ``ContainerParser`` doesn't
+actually do something in this example, we could optimize it and remove
+the use of ``IParser`` here, but we won't do this for consistency::
+
+  >>> class ContainerFactory(grok.GlobalUtility):
+  ...   grok.provides(IVcFactory)
+  ...   def __call__(self, path):
+  ...       parser = component.getUtility(IParser, '')
+  ...       container = Container()
+  ...       parser(container, path)
+  ...       return container
+  >>> grok.testing.grok_component('ContainerFactory', ContainerFactory)
+  True
+
+We need to maintain a list of everything modified or added, and a list
+of everything deleted by the update operation. Normally this
+information is extracted from the version control system, but for the
+purposes of this test we maintain it manually. In this case,
+everything is added so appears in the files list::
+
+  >>> checkout._files = [root.join('foo.test'), root.join('hoi.test'),
+  ...   root.join('sub'), root.join('sub', 'qux.test')]
+
+Nothing was removed::
+
+  >>> checkout._removed = []
+
+Let's load up the contents from the filesystem now, into a new container::
+
+  >>> container2 = Container()
+  >>> container2.__name__ = 'root'
+
+In order to load into a different container, we need to set up a new
+synchronizer with a new state::
+
+  >>> s = Synchronizer(checkout, TestState(container2))
+
+We can now do the loading::
+
+  >>> dummy = s.load(None)
+
+We expect the proper objects to be in the new container::
+
+  >>> sorted(container2.keys())
+  ['foo', 'hoi', 'sub']
+
+We check whether the items contains the right information::
+
+  >>> isinstance(container2['foo'], Item)
+  True
+  >>> container2['foo'].payload
+  1
+  >>> isinstance(container2['hoi'], Item)
+  True
+  >>> container2['hoi'].payload
+  16
+  >>> isinstance(container2['sub'], Container)
+  True
+  >>> sorted(container2['sub'].keys())
+  ['qux']
+  >>> container2['sub']['qux'].payload
+  3
+
+version control changes a file
+------------------------------
+
+Now we synchronize our checkout by synchronizing the checkout with the
+central coordinating server (or shared branch in case of a distributed
+version control system). We do a ``checkout.up()`` that causes the
+text in a file to be modified.
+
+The special checkout class we use for example purposes will call
+``update_function`` during an update. This function should then
+simulate what might happen during a version control system ``update``
+operation. Let's define one here that modifies text in a file::
+
+  >>> hoi_path = root.join('hoi.test')
+  >>> def update_function():
+  ...    hoi_path.write('200\n')
+  >>> checkout.update_function = update_function
+
+Now let's do an update::
+
+  >>> checkout.up()
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = [hoi_path]
+  >>> checkout._removed = []
+
+We will reload the checkout into Python objects::
+
+  >>> dummy = s.load(None)
+ 
+We expect the ``hoi`` object to be modified::
+
+  >>> container2['hoi'].payload
+  200
+
+version control adds a file
+---------------------------
+
+We update our checkout again and cause a file to be added::
+
+  >>> hallo = root.join('hallo.test').ensure()
+  >>> def update_function():
+  ...   hallo.write('300\n')
+  >>> checkout.update_function = update_function
+
+  >>> checkout.up()
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = [hallo]
+  >>> checkout._removed = []
+
+We will reload the checkout into Python objects again::
+
+  >>> dummy = s.load(None)
+ 
+We expect there to be a new object ``hallo``::
+
+  >>> 'hallo' in container2.keys()
+  True
+
+version control removes a file
+------------------------------
+
+We update our checkout and cause a file to be removed::
+
+  >>> def update_function():
+  ...   root.join('hallo.test').remove()
+  >>> checkout.update_function = update_function
+
+  >>> checkout.up()
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = []
+  >>> checkout._removed = [hallo]
+
+We will reload the checkout into Python objects::
+  
+  >>> dummy = s.load(None)
+
+We expect the object ``hallo`` to be gone again::
+
+  >>> 'hallo' in container2.keys()
+  False
+
+version control adds a directory
+--------------------------------
+
+We update our checkout and cause a directory (with a file inside) to be
+added::
+
+  >>> newdir_path = root.join('newdir')
+  >>> def update_function():
+  ...   newdir_path.ensure(dir=True)
+  ...   newfile_path = newdir_path.join('newfile.test').ensure()
+  ...   newfile_path.write('400\n')
+  >>> checkout.update_function = update_function
+  
+  >>> checkout.up()
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = [newdir_path, newdir_path.join('newfile.test')]
+  >>> checkout._removed = []
+
+Reloading this will cause a new container to exist::
+
+  >>> dummy = s.load(None)
+  >>> 'newdir' in container2.keys()
+  True
+  >>> isinstance(container2['newdir'], Container)
+  True
+  >>> container2['newdir']['newfile'].payload
+  400
+
+version control removes a directory
+-----------------------------------
+
+We update our checkout once again and cause a directory to be removed::
+
+  >>> def update_function():
+  ...   newdir_path.remove()
+  >>> checkout.update_function = update_function
+
+  >>> checkout.up()
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = []
+  >>> checkout._removed = [newdir_path, newdir_path.join('newfile.test')]
+
+And reload the data::
+
+  >>> dummy = s.load(None)
+
+Reloading this will cause the new container to be gone again::
+
+  >>> 'newdir' in container2.keys()
+  False
+
+version control changes a file into a directory
+-----------------------------------------------
+
+Some sequence of actions by other users has caused a name that previously
+referred to a file to now refer to a directory::
+
+  >>> hoi_path2 = root.join('hoi')
+  >>> def update_function():
+  ...   hoi_path.remove()
+  ...   hoi_path2.ensure(dir=True)
+  ...   some_path = hoi_path2.join('some.test').ensure(file=True)
+  ...   some_path.write('1000\n')
+  >>> checkout.update_function = update_function
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = [hoi_path2, hoi_path2.join('some.test')]
+  >>> checkout._removed = [hoi_path]
+
+  >>> checkout.up()
+
+Reloading this will cause a new container to be there instead of the file::
+
+  >>> dummy = s.load(None)
+  >>> isinstance(container2['hoi'], Container)
+  True
+  >>> container2['hoi']['some'].payload
+  1000
+
+version control changes a directory into a file
+-----------------------------------------------
+
+Some sequence of actions by other users has caused a name that
+previously referred to a directory to now refer to a file::
+
+  >>> def update_function():
+  ...   hoi_path2.remove()
+  ...   hoi_path = root.join('hoi.test').ensure()
+  ...   hoi_path.write('2000\n')
+  >>> checkout.update_function = update_function
+
+  >>> checkout.up()
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = [hoi_path]
+  >>> checkout._removed = [hoi_path2.join('some.test'), hoi_path2]
+
+Reloading this will cause a new item to be there instead of the
+container::
+
+  >>> dummy = s.load(None)
+  >>> isinstance(container2['hoi'], Item)
+  True
+  >>> container2['hoi'].payload
+  2000
+
+version control changes a file into one with a different file type
+------------------------------------------------------------------
+
+Some sequence of actions by other users has ccaused a name that
+previously referred to one type of object to now refer to another kind.
+Let's define an ``Item2``::
+
+  >>> class Item2(object):
+  ...   def __init__(self, payload):
+  ...     self.payload = payload
+
+And a parser and factory for it::
+  
+  >>> class Item2Parser(grok.GlobalUtility):
+  ...   grok.provides(IParser)
+  ...   grok.name('.test2')
+  ...   def __call__(self, object, path):
+  ...      object.payload = int(path.read()) ** 2
+  >>> grok.testing.grok_component('Item2Parser', Item2Parser)
+  True 
+  >>> class Item2Factory(grok.GlobalUtility):
+  ...   grok.provides(IVcFactory)
+  ...   grok.name('.test2')
+  ...   def __call__(self, path):
+  ...       parser = component.getUtility(IParser, '.test2')
+  ...       item = Item2(None) # dummy payload
+  ...       parser(item, path)
+  ...       return item
+  >>> grok.testing.grok_component('Item2Factory', Item2Factory)
+  True
+
+Now we define an update function that replaces ``hoi.test`` with
+``hoi.test2``::
+
+  >>> hoi_path3 = root.join('hoi.test2')
+  >>> def update_function():
+  ...    hoi_path.remove()
+  ...    hoi_path3.ensure()
+  ...    hoi_path3.write('44\n')
+  >>> checkout.update_function = update_function
+  >>> checkout.up()
+
+We maintain the list of things changed::
+
+  >>> checkout._files = [hoi_path3]
+  >>> checkout._removed = [hoi_path]
+
+Reloading this will cause a new type of item to be there instead of the old
+type::
+
+  >>> dummy = s.load(None)
+  >>> isinstance(container2['hoi'], Item2)
+  True
+  >>> container2['hoi'].payload
+  1936
+
+Let's restore the original ``hoi.test`` object::
+ 
+  >>> hoi_path3.remove()
+  >>> hoi_path.write('2000\n')
+  >>> del container2['hoi']
+  >>> container2['hoi'] = Item(2000)
+
+Complete synchronization
+------------------------
+
+Let's now exercise the ``sync`` method directly. First we'll modify
+the payload of the ``hoi`` item::
+
+  >>> container2['hoi'].payload = 3000
+ 
+Next, we willl add a new ``alpha`` file to the checkout when we do an
+``up()``, so again we simulate the actions of our version control system::
+
+  >>> alpha_path = root.join('alpha.test').ensure()
+  >>> def update_function():
+  ...   alpha_path.write('4000\n')
+  >>> checkout.update_function = update_function
+
+We maintain the lists of things changed::
+
+  >>> checkout._files = [alpha_path]
+  >>> checkout._removed = []
+
+The revision number before full synchronization::
+
+  >>> checkout.revision_nr()
+  8
+
+Now we'll synchronize with the memory structure. We'll pass a special
+function along that prints out all objects that have been created or
+modified::
+
+  >>> def f(obj):
+  ...   print "modified:", obj.__name__
+  >>> info = s.sync(None, message='', modified_function=f)
+  modified: alpha
+  >>> info.revision_nr
+  9
+
+We can get a report of what happened. No files were removed::
+
+  >>> info.files_removed()
+  []
+
+One file, alpha, was added to the checkout during our update (by
+someone else)::
+
+  >>> info.files_changed()
+  [local('.../root/alpha.test')]
+
+We removed no objects from our database since the last update::
+
+  >>> info.objects_removed()
+  []
+
+We did change one object, 'hoi', but the test infrastructure always returns
+all objects here (returning more objects is allowed)::
+
+  >>> info.objects_changed()
+  ['/root/foo', '/root/hoi', '/root', '/root/sub/qux', '/root/sub']
+
+We expect the checkout to reflect the changed state of the ``hoi`` object::
+
+  >>> root.join('hoi.test').read()
+  '3000\n'
+
+We also expect the database to reflect the creation of the new
+``alpha`` object::
+
+  >>> container2['alpha'].payload
+  4000
+

Modified: z3c.vcsync/trunk/src/z3c/vcsync/tests.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/tests.py	2008-04-21 19:06:11 UTC (rev 85568)
+++ z3c.vcsync/trunk/src/z3c/vcsync/tests.py	2008-04-21 19:07:29 UTC (rev 85569)
@@ -142,7 +142,7 @@
 def test_suite():
     suite = unittest.TestSuite([
         doctest.DocFileSuite(
-        'README.txt',
+        'internal.txt',
         setUp=setUpZope,
         tearDown=cleanUpZope,
         globs=globs,



More information about the Checkins mailing list