[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