[Checkins] SVN: Zope3/trunk/src/zope/app/fssync/ Added functional doctests and a test layer to zope.app.fssync.

Uwe Oestermeier uwe_oestermeier at iwm-kmrc.de
Wed Feb 28 11:16:54 EST 2007


Log message for revision 72908:
  Added functional doctests and a test layer to  zope.app.fssync.

Changed:
  U   Zope3/trunk/src/zope/app/fssync/browser/__init__.py
  A   Zope3/trunk/src/zope/app/fssync/fssync.txt
  A   Zope3/trunk/src/zope/app/fssync/ftesting.zcml
  A   Zope3/trunk/src/zope/app/fssync/ftests.py
  U   Zope3/trunk/src/zope/app/fssync/registration.txt
  A   Zope3/trunk/src/zope/app/fssync/testing.py

-=-
Modified: Zope3/trunk/src/zope/app/fssync/browser/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/fssync/browser/__init__.py	2007-02-28 16:08:37 UTC (rev 72907)
+++ Zope3/trunk/src/zope/app/fssync/browser/__init__.py	2007-02-28 16:16:53 UTC (rev 72908)
@@ -34,12 +34,15 @@
 
 def snarf_dir(response, dirname):
     """Helper to snarf a directory to the response."""
-    response.setStatus(200)
-    response.setHeader("Content-Type", "application/x-snarf")
     
-    temp = tempfile.TemporaryFile('wb')
+    temp = tempfile.TemporaryFile()
     snf = Snarfer(temp)
     snf.addtree(dirname)
+    temp.seek(0)
+    
+    response.setStatus(200)
+    response.setHeader('Content-type', 'application/x-snarf')
+    
     return temp
 
 class SnarfFile(BrowserView):

Added: Zope3/trunk/src/zope/app/fssync/fssync.txt
===================================================================
--- Zope3/trunk/src/zope/app/fssync/fssync.txt	2007-02-28 16:08:37 UTC (rev 72907)
+++ Zope3/trunk/src/zope/app/fssync/fssync.txt	2007-02-28 16:16:53 UTC (rev 72908)
@@ -0,0 +1,199 @@
+Using FSSync
+============
+
+The fssync package allows users to download objects from a Zope3 server to the local disk, edit the
+objects offline and synchronize the modifications with the server later on.
+
+Let's start with some basic infrastructure on the server side. We assume that a folder
+with some content already exists:
+
+>>> root = getRootFolder()
+>>> from zope.app.folder import Folder
+>>> serverfolder = root[u'test'] = Folder()
+>>> from zope.app.file import File
+>>> serverfile1 = serverfolder[u'file1.txt'] = File('A text file', 'plain/text')
+>>> serverfile2 = serverfolder[u'file2.txt'] = File('Another text file', 'plain/text')
+
+On the client side we need a directory for the initial checkout:
+
+>>> os.path.exists(checkoutdir)
+True
+
+
+Serialization format
+--------------------
+
+On the server side everything must be registered in a manner that we are allowed to access the 
+serialized data (see registration.txt for details). The serialized content is delivered in 
+a Zope3 specific SNARF archive.
+SNARF (Simple New ARchival Format) is a very simple format that basically puts one file 
+after another. Here we download it by calling the @@toFS.snarf view to give an impression of the
+internal structure of this format:
+
+>>> headers = {'Authorization': 'Basic globalmgr:globalmgrpw'}
+>>> conn = PublisherConnection('localhost')
+>>> conn.request('GET', 'test/@@toFS.snarf', headers=headers)
+>>> print conn.getresponse().read(-1)
+264 @@Zope/Annotations/test/@@Zope/Entries.xml
+<?xml version='1.0' encoding='utf-8'?>
+<entries>
+  <entry name="zope.app.dublincore.ZopeDublinCore"
+         type="zope.dublincore.annotatableadapter.ZDCAnnotationData"
+         factory="zope.dublincore.annotatableadapter.ZDCAnnotationData"
+         />
+</entries>
+...
+87 test/@@Zope/Extra/file1.txt/contentType
+<?xml version="1.0" encoding="utf-8" ?>
+<pickle> <string>plain/text</string> </pickle>
+132 test/@@Zope/Extra/file2.txt/@@Zope/Entries.xml
+<?xml version='1.0' encoding='utf-8'?>
+<entries>
+  <entry name="contentType"
+         type="__builtin__.str"
+         />
+</entries>
+87 test/@@Zope/Extra/file2.txt/contentType
+<?xml version="1.0" encoding="utf-8" ?>
+<pickle> <string>plain/text</string> </pickle>
+11 test/file1.txt
+A text file17 test/file2.txt
+Another text file
+
+Note that the main content is directly serialized whereas extra attributes and metadata are
+pickled in an XML format. These various aspects are saved on the local disk in numerous files.
+
+
+Initial Checkout
+----------------
+
+We perform an initial checkout to see what happens. We mimic the command line syntax 
+
+    zsync checkout http://user:password@host:port/path targetdir
+
+by using the corresponding FSSync command object. (The zsync script can be found in Zope3's
+topmost bin directory. Type ``zsync help`` for a list of available commands). 
+The FSSync object must be initialised with all relevant
+connection data and for the sake of this doctest with a special network instance.
+
+>>> from zope.fssync.fssync import FSSync
+>>> rooturl = 'http://globalmgr:globalmgrpw@localhost/test'
+>>> zsync = FSSync(network=TestNetwork(), rooturl=rooturl)
+
+Now we can call the checkout method:
+
+>>> zsync.checkout(checkoutdir)
+N .../test/
+U .../test/file1.txt
+N .../test/@@Zope/Extra/file1.txt/
+U .../test/@@Zope/Extra/file1.txt/contentType
+U .../test/file2.txt
+N .../test/@@Zope/Extra/file2.txt/
+U .../test/@@Zope/Extra/file2.txt/contentType
+N .../@@Zope/Annotations/test/
+U .../@@Zope/Annotations/test/zope.app.dublincore.ZopeDublinCore
+All done.
+
+The printout shows all new directories and updated files. As you can see, the file content is 
+directly mapped onto the filesystem whereas extra data and metadata are stored in special @@Zope 
+directories.
+
+Local Modifications
+-------------------
+
+Now we can edit the content and metadata on the local filesystem.
+
+>>> localdir = os.path.join(checkoutdir, 'test')
+>>> localfile1 = os.path.join(localdir, 'file1.txt')
+>>> fp = open(localfile1, 'w')
+>>> fp.write('A modified text file')
+>>> fp.close()
+
+The status command lists all local modifications:
+
+>>> zsync.status(localdir)
+/ .../test/
+M .../test/file1.txt
+= .../test/file2.txt
+
+If we want to add a file to the repository we must update the local list of entries by calling the
+add command explicitely:
+
+>>> newlocalfile = os.path.join(localdir, 'file3.txt')
+>>> fp = open(newlocalfile, 'w')
+>>> fp.write('A new local text file')
+>>> fp.close()
+
+>>> zsync.add(newlocalfile)
+A .../test/file3.txt
+
+>>> zsync.status(localdir)
+/ .../test/
+M .../test/file1.txt
+= .../test/file2.txt
+A .../test/file3.txt
+
+
+Commiting Modifications
+-----------------------
+
+Before we commit our local modifications we should check whether our local repository is still
+up to date. Let's say that by a coincidence someone else edited the same file on the server:
+
+>>> serverfile1.data = 'Ooops'
+>>> zsync.commit(localdir)
+Traceback (most recent call last):
+...
+Error: Up-to-date check failed:
+test/file1.txt
+
+We must update the local files and resolve all conflicts before we can proceed:
+
+>>> zsync.update(localdir)
+C .../test/file1.txt
+A .../test/file3.txt
+All done.
+
+The conflicts are marked in a diff3 manner:
+
+>>> print open(localfile1).read()
+<<<<<<< .../test/file1.txt
+A modified text file=======
+Ooops>>>>>>> .../test/file1.txt
+<BLANKLINE>
+
+Resolving the conflict is easy:
+
+>>> fp = open(localfile1, 'w')
+>>> fp.write('Oops, a modified text file.')
+>>> fp.close()
+>>> zsync.resolve(localfile1)
+
+Now we can commit our work:
+
+>>> zsync.commit(localdir)
+U .../test/file1.txt
+N .../test/@@Zope/Annotations/file1.txt/
+U .../test/@@Zope/Annotations/file1.txt/zope.app.dublincore.ZopeDublinCore
+U .../test/file3.txt
+N .../test/@@Zope/Extra/file3.txt/
+U .../test/@@Zope/Extra/file3.txt/contentType
+N .../test/@@Zope/Annotations/file3.txt/
+U .../test/@@Zope/Annotations/file3.txt/zope.app.dublincore.ZopeDublinCore
+U .../@@Zope/Annotations/test/zope.app.dublincore.ZopeDublinCore
+All done.
+
+Let's check whether the server objects have been updated accordingly:
+
+>>> serverfile1.data
+'Oops, a modified text file.'
+>>> u'file3.txt' in serverfolder.keys()
+True
+
+
+
+
+
+
+
+

Added: Zope3/trunk/src/zope/app/fssync/ftesting.zcml
===================================================================
--- Zope3/trunk/src/zope/app/fssync/ftesting.zcml	2007-02-28 16:08:37 UTC (rev 72907)
+++ Zope3/trunk/src/zope/app/fssync/ftesting.zcml	2007-02-28 16:16:53 UTC (rev 72908)
@@ -0,0 +1,60 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   i18n_domain="zope"
+   package="zope.app.fssync"
+   >
+
+  <!-- This file is the equivalent of site.zcml and it is -->
+  <!-- used for functional testing setup -->
+
+  <include package="zope.app.securitypolicy" file="meta.zcml" />
+
+  <include package="zope.app.zcmlfiles" />
+  <include package="zope.app.authentication" />
+  <include package="zope.app.securitypolicy" />
+  <include package="zope.app.file"/>
+  <include package="zope.app.folder"/>
+  <include package="zope.app.file.fssync"/>
+  <include package="zope.app.folder.fssync"/>
+  <include package="zope.dublincore.fssync"/>
+  <include package="zope.app.fssync"/>
+  
+  <securityPolicy
+    component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+
+  <role id="zope.Anonymous" title="Everybody"
+                 description="All users have this role implicitly" />
+  <role id="zope.Manager" title="Site Manager" />
+
+  <!-- Replace the following directive if you don't want public access -->
+  <grant permission="zope.View"
+                  role="zope.Anonymous" />
+  <grant permission="zope.app.dublincore.view"
+                  role="zope.Anonymous" />
+
+  <grantAll role="zope.Manager" />
+  <include package="zope.app.securitypolicy.tests" file="functional.zcml" />
+
+  <!-- Principals -->
+
+  <unauthenticatedPrincipal
+      id="zope.anybody"
+      title="Unauthenticated User" />
+
+  <!-- Principal that tests generally run as -->
+  <principal
+      id="zope.mgr"
+      title="Manager"
+      login="mgr"
+      password="mgrpw" />
+
+  <!-- Bootstrap principal used to make local grant to the principal above -->
+  <principal
+      id="zope.globalmgr"
+      title="Manager"
+      login="globalmgr"
+      password="globalmgrpw" />
+
+  <grant role="zope.Manager" principal="zope.globalmgr" />
+
+</configure>

Added: Zope3/trunk/src/zope/app/fssync/ftests.py
===================================================================
--- Zope3/trunk/src/zope/app/fssync/ftests.py	2007-02-28 16:08:37 UTC (rev 72907)
+++ Zope3/trunk/src/zope/app/fssync/ftests.py	2007-02-28 16:16:53 UTC (rev 72908)
@@ -0,0 +1,118 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""Functional fssync tests
+
+$Id: test_fssync.py 40495 2005-12-02 17:51:22Z efge $
+"""
+import unittest
+import os
+import shutil
+import time
+import tempfile
+import zope
+from cStringIO import StringIO
+from zope.testing import doctest
+from zope.testing import module
+from zope.testing import doctestunit
+from zope.app.testing import functional
+from zope.testbrowser.testing import PublisherConnection
+
+from zope.fssync import fssync
+from zope.fssync import fsutil
+
+from zope.app.fssync.testing import AppFSSyncLayer
+
+checkoutdir = tempfile.mkdtemp(prefix='checkoutdir')
+        
+class TestNetwork(fssync.Network):
+    """A specialization which uses a PublisherConnection suitable for functional doctests.
+    """
+
+    def httpreq(self, path, view, datasource=None,
+                content_type="application/x-snarf",
+                expected_type="application/x-snarf"):
+        """Issue an request. This is a overwritten version of the original Network.httpreq
+        method that uses a TestConnection as a replacement for httplib connections.
+        """
+        assert self.rooturl
+        if not path.endswith("/"):
+            path += "/"
+        path += view
+        conn = PublisherConnection(self.host_port)
+        headers = {}
+        if datasource is None:
+            method = 'GET'
+        else:
+            method = 'POST'
+            headers["Content-type"] = content_type
+            stream = StringIO()
+            datasource(stream)
+            headers["Content-Length"] = str(stream.tell())
+            
+        if self.user_passwd:
+            if ":" not in self.user_passwd:
+                auth = self.getToken(self.roottype,
+                                     self.host_port,
+                                     self.user_passwd)
+            else:
+                auth = self.createToken(self.user_passwd)
+            headers['Authorization'] = 'Basic %s' % auth
+        headers['Host'] = self.host_port
+        headers['Connection'] = 'close'
+
+        data = None
+        if datasource is not None:
+            data = stream.getvalue()
+            
+        conn.request(method, path, body=data, headers=headers)
+        response = conn.getresponse()
+         
+        if response.status != 200:
+            raise fsutil.Error("HTTP error %s (%s); error document:\n%s",
+                        response.status, response.reason,
+                        self.slurptext(response.content_as_file, response.msg))
+        elif expected_type and response.msg["Content-type"] != expected_type:
+            raise fsutil.Error(self.slurptext(response.content_as_file, response.msg))
+        else:
+            return response.content_as_file, response.msg    
+    
+def setUp(test):
+    module.setUp(test, 'zope.app.fssync.fssync_txt')
+    if not os.path.exists(checkoutdir):
+        os.mkdir(checkoutdir)
+
+def tearDown(test):
+    module.tearDown(test, 'zope.app.fssync.fssync_txt')
+    shutil.rmtree(checkoutdir)
+
+ 
+def test_suite():
+    
+    globs = {'os': os,
+            'zope':zope,
+            'pprint': doctestunit.pprint,
+            'checkoutdir': checkoutdir,
+            'PublisherConnection': PublisherConnection,
+            'TestNetwork': TestNetwork,
+            'sleep': time.sleep}
+     
+    suite = unittest.TestSuite()
+    test = functional.FunctionalDocFileSuite('fssync.txt',
+                             setUp=setUp, tearDown=tearDown, globs=globs,
+                             optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS)
+    test.layer = AppFSSyncLayer
+    suite.addTest(test)
+    return suite
+
+if __name__ == '__main__': unittest.main()

Modified: Zope3/trunk/src/zope/app/fssync/registration.txt
===================================================================
--- Zope3/trunk/src/zope/app/fssync/registration.txt	2007-02-28 16:08:37 UTC (rev 72907)
+++ Zope3/trunk/src/zope/app/fssync/registration.txt	2007-02-28 16:16:53 UTC (rev 72908)
@@ -63,8 +63,3 @@
 <zope.app.fssync.tests.sampleclass.CDefaultAdapter object at ...>
 
 
-
-To Dos
-======
-
-The zcml fssync namespace is no longer needed.

Added: Zope3/trunk/src/zope/app/fssync/testing.py
===================================================================
--- Zope3/trunk/src/zope/app/fssync/testing.py	2007-02-28 16:08:37 UTC (rev 72907)
+++ Zope3/trunk/src/zope/app/fssync/testing.py	2007-02-28 16:16:53 UTC (rev 72908)
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""zope.app.fssync common test related classes/functions/objects.
+
+$Id: testing.py 72426 2007-02-07 13:57:45Z baijum $
+"""
+
+__docformat__ = "reStructuredText"
+
+import os
+from zope.app.testing.functional import ZCMLLayer
+
+AppFSSyncLayer = ZCMLLayer(
+    os.path.join(os.path.split(__file__)[0], 'ftesting.zcml'),
+    __name__, 'AppFSSyncLayer', allow_teardown=True)



More information about the Checkins mailing list