Code

Implemented file store rollback. As a bonus, the hyperdb is now capable of
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 17 Dec 2001 03:52:48 +0000 (03:52 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 17 Dec 2001 03:52:48 +0000 (03:52 +0000)
storing more than one file per node - if a property name is supplied,
the file is called designator.property.
I decided not to migrate the existing files stored over to the new naming
scheme - the FileClass just doesn't specify the property name.

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@473 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
doc/announcement.txt
doc/index.html
roundup-admin
roundup/backends/back_anydbm.py
roundup/roundupdb.py
test/test_db.py

index 7d44745c06f6471c2f8c3c8d5797a7c37be48683..e2478978327590755da6e959588e553ad675ea62 100644 (file)
@@ -1,7 +1,7 @@
 This file contains the changes to the Roundup system over time. The entries
 are given with the most recent entry first.
 
 This file contains the changes to the Roundup system over time. The entries
 are given with the most recent entry first.
 
-2001-11-?? - 0.3.1
+2001-12-?? - 0.3.1b1
 Feature:
  . Added INSTANCE_NAME to configuration - used in web and email to identify
    the instance.
 Feature:
  . Added INSTANCE_NAME to configuration - used in web and email to identify
    the instance.
index f1ca352e1275dd8ebb720d422362eb007707e593..cdba40605b5220c517128612d8ccb839d52170f8 100644 (file)
@@ -1,16 +1,16 @@
-            Roundup 0.3.0 - an issue tracking system
+            Roundup 0.3.1b1 - an issue tracking system
 
 
-This release contains several new features which will require migration, so
-please read MIGRATION.txt!
+If you are upgrading from pre-0.3.0, please read MIGRATION.txt.
+
+Roundup requires python 2.1.1 for correct operation. Support for dumbdbm has
+been disabled until python 2.1.2 and 2.2 are released.
 
 Big stuff in this release:
 
 Big stuff in this release:
-  - lots of bug fixes, thanks to all users for their great feedback!
-  - much more flexible administration tool
-  - much better handling of errors
-  - more configuration options
-  - CGI login uses cookies instead of basic auth
-  - passwords are encoded in the database
-  - much, much more: see the CHANGES file for details.
+  - Use of transactions to prevent partial data commits
+  - Zope Product front-end
+  - Nicer, more consistent change message generation
+  - Several bug fixes
+  - Much, much more: see the CHANGES file for details.
 
 Source and documentation is available at the website:
      http://roundup.sourceforge.net/
 
 Source and documentation is available at the website:
      http://roundup.sourceforge.net/
index 59d2f35b4e46eea683fb7c7415607944933f14ea..b53b2b6659ce9485e3c59c85cce5fd5d4d0150a6 100644 (file)
@@ -2,7 +2,7 @@
 <title>Roundup: an Issue-Tracking System for Knowledge Workers</title>
 </head><body>
 
 <title>Roundup: an Issue-Tracking System for Knowledge Workers</title>
 </head><body>
 
-<h1 align=center>Roundup (0.3.0)</h1>
+<h1 align=center>Roundup (0.3.1)</h1>
 <h3 align=center>An Issue-Tracking System for Knowledge Workers</h2>
 
 <h1>Contents</h1>
 <h3 align=center>An Issue-Tracking System for Knowledge Workers</h2>
 
 <h1>Contents</h1>
 
 <h2><a name="requires">Prerequisites</a></h2>
 
 
 <h2><a name="requires">Prerequisites</a></h2>
 
-<dl>
-       <dd>Python 2.1.1 is required for the correct operation of roundup
-</dl>
+<p>
+Python 2.1.1 is required for the correct operation of roundup.
+</p>
 
 
+<p>
 Download the latest version from
 <a href="http://www.python.org/">http://www.python.org/</a>.
 Download the latest version from
 <a href="http://www.python.org/">http://www.python.org/</a>.
+</p>
 
 
 
 
 
 
@@ -342,96 +344,176 @@ Usage:
 </table>
 
 <p>
 </table>
 
 <p>
-<table border=1 cellspacing=0>
+<table width=100% border=1 cellspacing=0>
 <tr><th colspan=2>Command Help</th></tr>
 <tr><th colspan=2>Command Help</th></tr>
-<tr><td valign=top><strong>history</strong></td>
-    <td><tt>history designator</tt><p>
-    Lists the journal entries for the node identified by the designator.
-</td></tr>
-    
+
+
+<tr><td valign=top><strong>commit</strong></td>
+    <td><tt>Usage: commit</tt><p>
+<pre>
+The changes made during an interactive session are not
+automatically written to the database - they must be committed
+using this command.
+
+One-off commands on the command-line are automatically committed if
+they are successful.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>create</strong></td>
+    <td><tt>Usage: create classname property=value ...</tt><p>
+<pre>
+This creates a new entry of the given class using the property
+name=value arguments provided on the command line after the "create"
+command.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>display</strong></td>
+    <td><tt>Usage: display designator</tt><p>
+<pre>
+This lists the properties and their associated values for the given
+node.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>export</strong></td>
+    <td><tt>Usage: export class[,class] destination_dir</tt><p>
+<pre>
+This action exports the current data from the database into
+tab-separated-value files that are placed in the nominated destination
+directory. The journals are not exported.
+
+</pre></td></tr>
+
+
 <tr><td valign=top><strong>find</strong></td>
 <tr><td valign=top><strong>find</strong></td>
-    <td><tt>find classname propname=value ...</tt><p>
-    Find the nodes of the given class with a given property value. The
-    value may be either the nodeid of the linked node, or its key value.
-</td></tr>
-    
+    <td><tt>Usage: find classname propname=value ...</tt><p>
+<pre>
+Find the nodes of the given class with a given link property value. The
+value may be either the nodeid of the linked node, or its key value.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>get</strong></td>
+    <td><tt>Usage: get property designator[,designator]*</tt><p>
+<pre>
+Retrieves the property value of the nodes specified by the designators.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>help</strong></td>
+    <td><tt>Usage: help topic</tt><p>
+<pre>
+commands  -- list commands
+<command> -- help specific to a command
+initopts  -- init command options
+all       -- all available help
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>history</strong></td>
+    <td><tt>Usage: history designator</tt><p>
+<pre>
+Lists the journal entries for the node identified by the designator.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>import</strong></td>
+    <td><tt>Usage: import class file</tt><p>
+<pre>
+The file must define the same properties as the class (including having
+a "header" line with those property names.) The new nodes are added to
+the existing database - if you want to create a new database using the
+imported data, then create a new database (or, tediously, retire all
+the old data.)
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>initialise</strong></td>
+    <td><tt>Usage: initialise [template [backend [admin password]]]</tt><p>
+<pre>
+The command will prompt for the instance home directory (if not supplied
+through INSTANCE_HOME or the -i option. The template, backend and admin
+password may be specified on the command-line as arguments, in that
+order.
+
+See also initopts help.
+
+</pre></td></tr>
+
+
 <tr><td valign=top><strong>list</strong></td>
 <tr><td valign=top><strong>list</strong></td>
-    <td><tt>list classname [property]</tt><p>
-    Lists all instances of the given class along. If the property is not
-    specified, the  "label" property is used. The label property is tried
-    in order: the key, "name", "title" and then the first property,
-    alphabetically.
-</td></tr>
-    
+    <td><tt>Usage: list classname [property]</tt><p>
+<pre>
+Lists all instances of the given class. If the property is not
+specified, the  "label" property is used. The label property is tried
+in order: the key, "name", "title" and then the first property,
+alphabetically.
+
+</pre></td></tr>
+
+
 <tr><td valign=top><strong>retire</strong></td>
 <tr><td valign=top><strong>retire</strong></td>
-    <td><tt>retire designator[,designator]*</tt><p>
-    This action indicates that a particular node is not to be retrieved by
-    the list or find commands, and its key value may be re-used.
-</td></tr>
-    
-<tr><td valign=top><strong>create</strong></td>
-    <td><tt>create classname property=value ...</tt><p>
-    This creates a new entry of the given class using the property
-    name=value arguments provided on the command line after the "create"
-    command.
-</td></tr>
-    
-<tr><td valign=top><strong>get</strong></td>
-    <td><tt>get property designator[,designator]*</tt><p>
-    Retrieves the property value of the nodes specified by the designators.
-</td></tr>
-    
-<tr><td valign=top><strong>spec</strong></td>
-    <td><tt>spec classname</tt><p>
-    This lists the properties for a given class.
-</td></tr>
-    
+    <td><tt>Usage: retire designator[,designator]*</tt><p>
+<pre>
+This action indicates that a particular node is not to be retrieved by
+the list or find commands, and its key value may be re-used.
+
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>rollback</strong></td>
+    <td><tt>Usage: rollback</tt><p>
+<pre>
+The changes made during an interactive session are not
+automatically written to the database - they must be committed
+manually. This command undoes all those changes, so a commit
+immediately after would make no changes to the database.
+
+</pre></td></tr>
+
+
 <tr><td valign=top><strong>set</strong></td>
 <tr><td valign=top><strong>set</strong></td>
-    <td><tt>set designator[,designator]* propname=value ...</tt><p>
-    Sets the property to the value for all designators given.
-</td></tr>
+    <td><tt>Usage: set designator[,designator]* propname=value ...</tt><p>
+<pre>
+Sets the property to the value for all designators given.
 
 
-<tr><td valign=top><strong>init</strong></td>
-    <td><tt>init [template [backend [admin password]]]</tt><p>
-    The command will prompt for the instance home directory (if not supplied
-    through INSTANCE_HOME or the -i option. The template, backend and admin
-    password may be specified on the command-line as arguments, in that order.
-</td></tr>
+</pre></td></tr>
 
 
-<tr><td valign=top><strong>export</strong></td>
-    <td><tt>export class[,class] destination_dir</tt><p>
-    This action exports the current data from the database into
-    comma-separated files that are placed in the nominated destination
-    directory. The journals are not exported.
-</td></tr>
 
 
-<tr><td valign=top><strong>import</strong></td>
-    <td><tt>import class file</tt><p>
-    The file must define the same properties as the class (including having
-    a "header" line with those property names.)
-</td></tr>
-    
-<tr><td valign=top><strong>freshen</strong></td>
-    <td><tt>freshen</tt><p>
-        <strong>**DO NOT USE**</strong>
-        <p>
-    This currently kills databases!!!!
-    <p>
-    This action should generally not be used. It reads in an instance
-    database and writes it again. In the future, is may also update
-    instance code to account for changes in templates. It's probably wise
-    not to use it anyway. Until we're sure it won't break things...
-</td></tr>
+<tr><td valign=top><strong>specification</strong></td>
+    <td><tt>Usage: specification classname</tt><p>
+<pre>
+This lists the properties for a given class.
 
 
-<tr><td><strong>help</strong></td>
-    <td><tt>help [command]</tt><p>
-    Short help about roundup-admin or the specific command.
-    </td></tr>
+</pre></td></tr>
+
+
+<tr><td valign=top><strong>table</strong></td>
+    <td><tt>Usage: table classname [property[,property]*]</tt><p>
+<pre>
+Lists all instances of the given class. If the properties are not
+specified, all properties are displayed. By default, the column widths
+are the width of the property names. The width may be explicitly defined
+by defining the property as "name:width". For example::
+  roundup> table priority id,name:10
+  Id Name
+  1  fatal-bug 
+  2  bug       
+  3  usability 
+  4  feature   
+
+</pre></td></tr>
 
 
-<tr><td><strong>morehelp</strong></td>
-    <td><tt>morehelp</tt><p>
-    All available help from the roundup-admin tool.
-    </td></tr>
 </table>
 
 <p>
 </table>
 
 <p>
@@ -1197,7 +1279,7 @@ system on their time.
 
 <p>&nbsp;</p>
 <hr>
 
 <p>&nbsp;</p>
 <hr>
-$Id: index.html,v 1.21 2001-12-13 00:20:01 richard Exp $
+$Id: index.html,v 1.22 2001-12-17 03:52:47 richard Exp $
 <p>&nbsp;</p>
 
 </body></html>
 <p>&nbsp;</p>
 
 </body></html>
index 2a4162e98565976e44e6abcc33b148dd980c62fc..6a1d5c078efa1716ccc4a5e9e17d209857a4a983 100755 (executable)
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup-admin,v 1.54 2001-12-15 23:09:23 richard Exp $
+# $Id: roundup-admin,v 1.55 2001-12-17 03:52:47 richard Exp $
 
 # python version check
 from roundup import version_check
 
 # python version check
 from roundup import version_check
@@ -93,6 +93,28 @@ Options:
         print '\n'.join(commands)
         print
 
         print '\n'.join(commands)
         print
 
+    def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')):
+       commands = self.commands.values()
+        def sortfun(a, b):
+            return cmp(a.__name__, b.__name__)
+        commands.sort(sortfun)
+       for command in commands:
+            h = command.__doc__.split('\n')
+            name = command.__name__[3:]
+            usage = h[0]
+            print '''
+<tr><td valign=top><strong>%(name)s</strong></td>
+    <td><tt>%(usage)s</tt><p>
+<pre>'''%locals()
+            indent = indent_re.match(h[3])
+            if indent: indent = len(indent.group(1))
+            for line in h[3:]:
+                if indent:
+                    print line[indent:]
+                else:
+                    print line
+            print '</pre></td></tr>\n'
+
     def help_all(self):
         print '''
 All commands (except help) require an instance specifier. This is just the path
     def help_all(self):
         print '''
 All commands (except help) require an instance specifier. This is just the path
@@ -978,6 +1000,9 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.54  2001/12/15 23:09:23  richard
+# Some cleanups in roundup-admin, also made it work again...
+#
 # Revision 1.53  2001/12/13 00:20:00  richard
 #  . Centralised the python version check code, bumped version to 2.1.1 (really
 #    needs to be 2.1.2, but that isn't released yet :)
 # Revision 1.53  2001/12/13 00:20:00  richard
 #  . Centralised the python version check code, bumped version to 2.1.1 (really
 #    needs to be 2.1.2, but that isn't released yet :)
index b11a461513e5fe6a29b1a9dd89a674f4e0164038..0d52750cf605106f40fe1de70129962023924575 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_anydbm.py,v 1.18 2001-12-16 10:53:38 richard Exp $
+#$Id: back_anydbm.py,v 1.19 2001-12-17 03:52:48 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
@@ -245,6 +245,36 @@ class Database(hyperdb.Database):
         res = res + db.keys()
         return res
 
         res = res + db.keys()
         return res
 
+
+    #
+    # Files - special node properties
+    #
+    def filename(self, classname, nodeid, property=None):
+        '''Determine what the filename for the given node and optionally property is.
+        '''
+        # TODO: split into multiple files directories
+        if property:
+            return os.path.join(self.dir, 'files', '%s%s.%s'%(classname,
+                nodeid, property))
+        else:
+            # roundupdb.FileClass never specified the property name, so don't include it
+            return os.path.join(self.dir, 'files', '%s%s'%(classname,
+                nodeid))
+
+    def storefile(self, classname, nodeid, property, content):
+        '''Store the content of the file in the database. The property may be None, in
+           which case the filename does not indicate which property is being saved.
+        '''
+        name = self.filename(classname, nodeid, property)
+        open(name + '.tmp', 'wb').write(content)
+        self.transactions.append((self._doStoreFile, (name, )))
+
+    def getfile(self, classname, nodeid, property):
+        '''Store the content of the file in the database.
+        '''
+        return open(self.filename(classname, nodeid, property), 'rb').read()
+
+
     #
     # Journal
     #
     #
     # Journal
     #
@@ -291,11 +321,20 @@ class Database(hyperdb.Database):
         '''
         if DEBUG:
             print 'commit', (self,)
         '''
         if DEBUG:
             print 'commit', (self,)
-        # lock the DB
+        # TODO: lock the DB
+
+        # keep a handle to all the database files opened
+        self.databases = {}
+
+        # now, do all the transactions
         for method, args in self.transactions:
         for method, args in self.transactions:
-            # TODO: optimise this, duh!
             method(*args)
             method(*args)
-        # unlock the DB
+
+        # now close all the database files
+        for db in self.databases.values():
+            db.close()
+        del self.databases
+        # TODO: unlock the DB
 
         # all transactions committed, back to normal
         self.cache = {}
 
         # all transactions committed, back to normal
         self.cache = {}
@@ -306,17 +345,31 @@ class Database(hyperdb.Database):
     def _doSaveNode(self, classname, nodeid, node):
         if DEBUG:
             print '_doSaveNode', (self, classname, nodeid, node)
     def _doSaveNode(self, classname, nodeid, node):
         if DEBUG:
             print '_doSaveNode', (self, classname, nodeid, node)
-        db = self.getclassdb(classname, 'c')
+
+        # get the database handle
+        db_name = 'nodes.%s'%classname
+        if self.databases.has_key(db_name):
+            db = self.databases[db_name]
+        else:
+            db = self.databases[db_name] = self.getclassdb(classname, 'c')
+
         # now save the marshalled data
         db[nodeid] = marshal.dumps(node)
         # now save the marshalled data
         db[nodeid] = marshal.dumps(node)
-        db.close()
 
     def _doSaveJournal(self, classname, nodeid, action, params):
         if DEBUG:
             print '_doSaveJournal', (self, classname, nodeid, action, params)
         entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
             params)
 
     def _doSaveJournal(self, classname, nodeid, action, params):
         if DEBUG:
             print '_doSaveJournal', (self, classname, nodeid, action, params)
         entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
             params)
-        db = self._opendb('journals.%s'%classname, 'c')
+
+        # get the database handle
+        db_name = 'journals.%s'%classname
+        if self.databases.has_key(db_name):
+            db = self.databases[db_name]
+        else:
+            db = self.databases[db_name] = self._opendb(db_name, 'c')
+
+        # now insert the journal entry
         if db.has_key(nodeid):
             s = db[nodeid]
             l = marshal.loads(db[nodeid])
         if db.has_key(nodeid):
             s = db[nodeid]
             l = marshal.loads(db[nodeid])
@@ -324,13 +377,20 @@ class Database(hyperdb.Database):
         else:
             l = [entry]
         db[nodeid] = marshal.dumps(l)
         else:
             l = [entry]
         db[nodeid] = marshal.dumps(l)
-        db.close()
+
+    def _doStoreFile(self, name, **databases):
+        # the file is currently ".tmp" - move it to its real name to commit
+        os.rename(name+".tmp", name)
 
     def rollback(self):
         ''' Reverse all actions from the current transaction.
         '''
         if DEBUG:
             print 'rollback', (self, )
 
     def rollback(self):
         ''' Reverse all actions from the current transaction.
         '''
         if DEBUG:
             print 'rollback', (self, )
+        for method, args in self.transactions:
+            # delete temporary files
+            if method == self._doStoreFile:
+                os.remove(args[0]+".tmp")
         self.cache = {}
         self.dirtynodes = {}
         self.newnodes = {}
         self.cache = {}
         self.dirtynodes = {}
         self.newnodes = {}
@@ -338,6 +398,10 @@ class Database(hyperdb.Database):
 
 #
 #$Log: not supported by cvs2svn $
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.18  2001/12/16 10:53:38  richard
+#take a copy of the node dict so that the subsequent set
+#operation doesn't modify the oldvalues structure
+#
 #Revision 1.17  2001/12/14 23:42:57  richard
 #yuck, a gdbm instance tests false :(
 #I've left the debugging code in - it should be removed one day if we're ever
 #Revision 1.17  2001/12/14 23:42:57  richard
 #yuck, a gdbm instance tests false :(
 #I've left the debugging code in - it should be removed one day if we're ever
index f57139ed49d33716f6779915d051773325dc5a58..50cd90c4dcca11d9a69076be33364dba63e56d3a 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundupdb.py,v 1.33 2001-12-16 10:53:37 richard Exp $
+# $Id: roundupdb.py,v 1.34 2001-12-17 03:52:48 richard Exp $
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
@@ -188,28 +188,14 @@ class FileClass(Class):
         content = propvalues['content']
         del propvalues['content']
         newid = Class.create(self, **propvalues)
         content = propvalues['content']
         del propvalues['content']
         newid = Class.create(self, **propvalues)
-        self.setcontent(self.classname, newid, content)
+        self.db.storefile(self.classname, newid, None, content)
         return newid
 
         return newid
 
-    def filename(self, classname, nodeid):
-        # TODO: split into multiple files directories
-        return os.path.join(self.db.dir, 'files', '%s%s'%(classname, nodeid))
-
-    def setcontent(self, classname, nodeid, content):
-        ''' set the content file for this file
-        '''
-        open(self.filename(classname, nodeid), 'wb').write(content)
-
-    def getcontent(self, classname, nodeid):
-        ''' get the content file for this file
-        '''
-        return open(self.filename(classname, nodeid), 'rb').read()
-
     def get(self, nodeid, propname, default=_marker):
         ''' trap the content propname and get it from the file
         '''
         if propname == 'content':
     def get(self, nodeid, propname, default=_marker):
         ''' trap the content propname and get it from the file
         '''
         if propname == 'content':
-            return self.getcontent(self.classname, nodeid)
+            return self.db.getfile(self.classname, nodeid, None)
         if default is not _marker:
             return Class.get(self, nodeid, propname, default)
         else:
         if default is not _marker:
             return Class.get(self, nodeid, propname, default)
         else:
@@ -506,6 +492,10 @@ class IssueClass(Class):
 
 #
 # $Log: not supported by cvs2svn $
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.33  2001/12/16 10:53:37  richard
+# take a copy of the node dict so that the subsequent set
+# operation doesn't modify the oldvalues structure
+#
 # Revision 1.32  2001/12/15 23:48:35  richard
 # Added ROUNDUPDBSENDMAILDEBUG so one can test the sendmail method without
 # actually sending mail :)
 # Revision 1.32  2001/12/15 23:48:35  richard
 # Added ROUNDUPDBSENDMAILDEBUG so one can test the sendmail method without
 # actually sending mail :)
index 08b918adebc927eff22b37d4f5f6b738fbf7dd50..c0919f569337b9f11da1467c0857dc3929ae4cb0 100644 (file)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: test_db.py,v 1.11 2001-12-10 23:17:20 richard Exp $ 
+# $Id: test_db.py,v 1.12 2001-12-17 03:52:48 richard Exp $ 
 
 import unittest, os, shutil
 
 from roundup.hyperdb import String, Password, Link, Multilink, Date, \
     Interval, Class, DatabaseError
 
 import unittest, os, shutil
 
 from roundup.hyperdb import String, Password, Link, Multilink, Date, \
     Interval, Class, DatabaseError
+from roundup.roundupdb import FileClass
 
 def setupSchema(db, create):
     status = Class(db, "status", name=String())
 
 def setupSchema(db, create):
     status = Class(db, "status", name=String())
@@ -33,6 +34,7 @@ def setupSchema(db, create):
     Class(db, "user", username=String(), password=Password())
     Class(db, "issue", title=String(), status=Link("status"),
         nosy=Multilink("user"))
     Class(db, "user", username=String(), password=Password())
     Class(db, "issue", title=String(), status=Link("status"),
         nosy=Multilink("user"))
+    FileClass(db, "file", name=String(), type=String())
     db.commit()
 
 class MyTestCase(unittest.TestCase):
     db.commit()
 
 class MyTestCase(unittest.TestCase):
@@ -46,7 +48,7 @@ class anydbmDBTestCase(MyTestCase):
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
-        os.mkdir('_test_dir')
+        os.makedirs('_test_dir/files')
         self.db = anydbm.Database('_test_dir', 'test')
         setupSchema(self.db, 1)
 
         self.db = anydbm.Database('_test_dir', 'test')
         setupSchema(self.db, 1)
 
@@ -73,6 +75,11 @@ class anydbmDBTestCase(MyTestCase):
 
     def testTransactions(self):
         num_issues = len(self.db.issue.list())
 
     def testTransactions(self):
         num_issues = len(self.db.issue.list())
+        files_dir = os.path.join('_test_dir', 'files')
+        if os.path.exists(files_dir):
+            num_files = len(os.listdir(files_dir))
+        else:
+            num_files = 0
         self.db.issue.create(title="don't commit me!", status='1')
         self.assertNotEqual(num_issues, len(self.db.issue.list()))
         self.db.rollback()
         self.db.issue.create(title="don't commit me!", status='1')
         self.assertNotEqual(num_issues, len(self.db.issue.list()))
         self.db.rollback()
@@ -83,6 +90,18 @@ class anydbmDBTestCase(MyTestCase):
         self.assertNotEqual(num_issues, len(self.db.issue.list()))
         self.db.rollback()
         self.assertNotEqual(num_issues, len(self.db.issue.list()))
         self.assertNotEqual(num_issues, len(self.db.issue.list()))
         self.db.rollback()
         self.assertNotEqual(num_issues, len(self.db.issue.list()))
+        self.db.file.create(name="test", type="text/plain", content="hi")
+        self.db.rollback()
+        self.assertEqual(num_files, len(os.listdir(files_dir)))
+        self.db.file.create(name="test", type="text/plain", content="hi")
+        self.db.commit()
+        self.assertNotEqual(num_files, len(os.listdir(files_dir)))
+        num_files2 = len(os.listdir(files_dir))
+        self.db.file.create(name="test", type="text/plain", content="hi")
+        self.db.rollback()
+        self.assertNotEqual(num_files, len(os.listdir(files_dir)))
+        self.assertEqual(num_files2, len(os.listdir(files_dir)))
+
 
     def testExceptions(self):
         # this tests the exceptions that should be raised
 
     def testExceptions(self):
         # this tests the exceptions that should be raised
@@ -156,7 +175,7 @@ class anydbmReadOnlyDBTestCase(MyTestCase):
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
-        os.mkdir('_test_dir')
+        os.makedirs('_test_dir/files')
         db = anydbm.Database('_test_dir', 'test')
         setupSchema(db, 1)
         self.db = anydbm.Database('_test_dir')
         db = anydbm.Database('_test_dir', 'test')
         setupSchema(db, 1)
         self.db = anydbm.Database('_test_dir')
@@ -178,7 +197,7 @@ class bsddbDBTestCase(anydbmDBTestCase):
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
-        os.mkdir('_test_dir')
+        os.makedirs('_test_dir/files')
         self.db = bsddb.Database('_test_dir', 'test')
         setupSchema(self.db, 1)
 
         self.db = bsddb.Database('_test_dir', 'test')
         setupSchema(self.db, 1)
 
@@ -188,7 +207,7 @@ class bsddbReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
-        os.mkdir('_test_dir')
+        os.makedirs('_test_dir/files')
         db = bsddb.Database('_test_dir', 'test')
         setupSchema(db, 1)
         self.db = bsddb.Database('_test_dir')
         db = bsddb.Database('_test_dir', 'test')
         setupSchema(db, 1)
         self.db = bsddb.Database('_test_dir')
@@ -201,7 +220,7 @@ class bsddb3DBTestCase(anydbmDBTestCase):
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
-        os.mkdir('_test_dir')
+        os.makedirs('_test_dir/files')
         self.db = bsddb3.Database('_test_dir', 'test')
         setupSchema(self.db, 1)
 
         self.db = bsddb3.Database('_test_dir', 'test')
         setupSchema(self.db, 1)
 
@@ -211,7 +230,7 @@ class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
         # remove previous test, ignore errors
         if os.path.exists('_test_dir'):
             shutil.rmtree('_test_dir')
-        os.mkdir('_test_dir')
+        os.makedirs('_test_dir/files')
         db = bsddb3.Database('_test_dir', 'test')
         setupSchema(db, 1)
         self.db = bsddb3.Database('_test_dir')
         db = bsddb3.Database('_test_dir', 'test')
         setupSchema(db, 1)
         self.db = bsddb3.Database('_test_dir')
@@ -241,6 +260,9 @@ def suite():
 
 #
 # $Log: not supported by cvs2svn $
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.11  2001/12/10 23:17:20  richard
+# Added transaction tests to test_db
+#
 # Revision 1.10  2001/12/03 21:33:39  richard
 # Fixes so the tests use commit and not close
 #
 # Revision 1.10  2001/12/03 21:33:39  richard
 # Fixes so the tests use commit and not close
 #