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.
 
-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.
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:
-  - 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/
index 59d2f35b4e46eea683fb7c7415607944933f14ea..b53b2b6659ce9485e3c59c85cce5fd5d4d0150a6 100644 (file)
@@ -2,7 +2,7 @@
 <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>
 
 <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>.
+</p>
 
 
 
@@ -342,96 +344,176 @@ Usage:
 </table>
 
 <p>
-<table border=1 cellspacing=0>
+<table width=100% border=1 cellspacing=0>
 <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>
-    <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>
-    <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>
-    <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>
-    <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>
@@ -1197,7 +1279,7 @@ system on their time.
 
 <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>
index 2a4162e98565976e44e6abcc33b148dd980c62fc..6a1d5c078efa1716ccc4a5e9e17d209857a4a983 100755 (executable)
@@ -16,7 +16,7 @@
 # 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
@@ -93,6 +93,28 @@ Options:
         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
@@ -978,6 +1000,9 @@ if __name__ == '__main__':
 
 #
 # $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 :)
index b11a461513e5fe6a29b1a9dd89a674f4e0164038..0d52750cf605106f40fe1de70129962023924575 100644 (file)
@@ -15,7 +15,7 @@
 # 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
@@ -245,6 +245,36 @@ class Database(hyperdb.Database):
         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
     #
@@ -291,11 +321,20 @@ class Database(hyperdb.Database):
         '''
         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:
-            # TODO: optimise this, duh!
             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 = {}
@@ -306,17 +345,31 @@ class Database(hyperdb.Database):
     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)
-        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)
-        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])
@@ -324,13 +377,20 @@ class Database(hyperdb.Database):
         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, )
+        for method, args in self.transactions:
+            # delete temporary files
+            if method == self._doStoreFile:
+                os.remove(args[0]+".tmp")
         self.cache = {}
         self.dirtynodes = {}
         self.newnodes = {}
@@ -338,6 +398,10 @@ class Database(hyperdb.Database):
 
 #
 #$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
index f57139ed49d33716f6779915d051773325dc5a58..50cd90c4dcca11d9a69076be33364dba63e56d3a 100644 (file)
@@ -15,7 +15,7 @@
 # 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.
@@ -188,28 +188,14 @@ class FileClass(Class):
         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
 
-    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':
-            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:
@@ -506,6 +492,10 @@ class IssueClass(Class):
 
 #
 # $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 :)
index 08b918adebc927eff22b37d4f5f6b738fbf7dd50..c0919f569337b9f11da1467c0857dc3929ae4cb0 100644 (file)
 # 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
+from roundup.roundupdb import FileClass
 
 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"))
+    FileClass(db, "file", name=String(), type=String())
     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')
-        os.mkdir('_test_dir')
+        os.makedirs('_test_dir/files')
         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())
+        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()
@@ -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.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
@@ -156,7 +175,7 @@ class anydbmReadOnlyDBTestCase(MyTestCase):
         # 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')
@@ -178,7 +197,7 @@ class bsddbDBTestCase(anydbmDBTestCase):
         # 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)
 
@@ -188,7 +207,7 @@ class bsddbReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
         # 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')
@@ -201,7 +220,7 @@ class bsddb3DBTestCase(anydbmDBTestCase):
         # 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)
 
@@ -211,7 +230,7 @@ class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
         # 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')
@@ -241,6 +260,9 @@ def suite():
 
 #
 # $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
 #