PATCH: Deprecate special-purpose database methods
Mark Mitchell
mark at codesourcery.com
Thu Jul 21 05:12:50 UTC 2005
In the early history of QMTest, the various things in a database
(namely tests, resources, and suites) were completely disaparate
entities. Eventually, we reworked things so that all of these
entities were instances of "Extension". However, an ugly wart
remained: we still had Database.GetTest, Database.GetResource, and
Database.GetSuite.
This patch adds Database.GetExtension, and new databases should
override that method, rather than the old methods. See the updated
documentation attached for more information.
As a side-effect, writing database classes that permit multiple
entries with the same name (like having a test and resource both named
"foo") is no longer supported. That was never an intentional feature;
it was merely an accident of the original implementation, and breaks
the conceptual model of a Database as logically similar to a
filesystem. Existing database classes will continue to work, but any
dependence on this time is deprecated.
Committed.
--
Mark Mitchell
CodeSourcery, LLC
mark at codesourcery.com
2005-07-20 Mark Mitchell <mark at codesourcery.com>
* qm/test/database.py (qm.test.resource.Resource): Import it.
(qm.test.suite.Suite): Likewise.
(Database._is_generic_database): New
variable.
(Database.GetExtension): New function.
(Database.GetTest): Use it.
(Database.GetResource): Likewise.
(Database.GetSuite): Likewise.
* qm/test/doc/reference.xml: Update information about writing
database classes.
Index: qm/test/database.py
===================================================================
RCS file: /home/qm/Repository/qm/qm/test/database.py,v
retrieving revision 1.42
diff -c -5 -p -r1.42 database.py
*** qm/test/database.py 31 May 2005 15:48:37 -0000 1.42
--- qm/test/database.py 21 Jul 2005 05:04:48 -0000
*************** import qm.extension
*** 24,33 ****
--- 24,35 ----
import qm.fields
from qm.label import *
from qm.test.base import *
from qm.test.directory_suite import DirectorySuite
from qm.test.runnable import Runnable
+ from qm.test.resource import Resource
+ from qm.test.suite import Suite
from qm.test.test import Test
########################################################################
# Variables
########################################################################
*************** class Database(qm.extension.Extension):
*** 493,502 ****
--- 495,513 ----
}
"""The exceptions to be raised when a particular item cannot be found.
This map is indexed by the 'ITEM_KINDS'; the value indicates the
exception class to be used when the indicated kind cannot be found."""
+
+ _is_generic_database = False
+ """True if this database implements 'GetExtension' as a primitive.
+
+ Databases should implement 'GetExtension' and then override
+ '_is_generic_database', setting it to 'True'. However, legacy
+ databases implemented 'GetTest', 'GetResource', and 'GetSuite' as
+ primivites. These legacy databases should not override
+ '_generic_database'."""
kind = "database"
"""The 'Extension' kind."""
def __init__(self, path, arguments):
*************** class Database(qm.extension.Extension):
*** 590,600 ****
components.append(label)
break
return components
!
# Methods that deal with tests.
def GetTest(self, test_id):
"""Return the 'TestDescriptor' for the test named 'test_id'.
--- 601,669 ----
components.append(label)
break
return components
!
! # Generic methods that deal with extensions.
!
! def GetExtension(self, id):
! """Return the extension object named 'id'.
!
! 'id' -- The label for the extension.
!
! returns -- The instance of 'Extension' with the indicated name,
! or 'None' if there is no such entity.
!
! Database classes should override this method, and then define
! 'GetTest', 'GetResource', and 'GetSuite' in terms of this
! method. However, for backwards compatibility, this base class
! implements this generic method in terms of the special-purpose
! methods."""
!
! for kind in (Database.TEST, Database.RESOURCE):
! try:
! item = self.GetItem(kind, id).GetItem()
! except NoSuchItemError:
! pass
!
! try:
! return self.GetSuite(id)
! except NoSuchSuiteError:
! pass
!
! return None
!
!
! def RemoveExtension(self, id, kind):
! """Remove the extension 'id' from the database.
!
! 'id' -- A label for the 'Extension' instance stored in the
! database.
!
! 'kind' -- The kind of 'Extension' stored with the given 'id'."""
!
! raise NotImplementedError
!
!
! def WriteExtension(self, id, extension):
! """Store 'extension' in the database, using the name 'id'.
!
! 'id' -- A label for the 'extension'.
!
! 'extension' -- An instance of 'Extension'.
!
! The 'extension' is stored in the database. If there is a
! previous item in the database with the same id, it is removed
! and replaced with 'extension'. Some databases may not be able
! to store all 'Extension' instances; those database must throw an
! exception when an attempt is made to store such an
! 'extension'."""
!
! raise NotImplementedError
!
!
# Methods that deal with tests.
def GetTest(self, test_id):
"""Return the 'TestDescriptor' for the test named 'test_id'.
*************** class Database(qm.extension.Extension):
*** 603,612 ****
--- 672,689 ----
returns -- A 'TestDescriptor' corresponding to 'test_id'.
raises -- 'NoSuchTestError' if there is no test in the database
named 'test_id'."""
+ if self._is_generic_database:
+ test = self.GetExtension(test_id)
+ if isinstance(test, Test):
+ return TestDescriptor(self,
+ test_id,
+ test.GetClassName(),
+ test.GetExplicitArguments())
+
raise NoSuchTestError(test_id)
def HasTest(self, test_id):
"""Check whether or not the database has a test named 'test_id'.
*************** class Database(qm.extension.Extension):
*** 665,674 ****
--- 742,756 ----
subdirectories."""
if suite_id == "":
return DirectorySuite(self, "")
+ if self._is_generic_database:
+ suite = GetExtension(suite_id)
+ if isinstance(suite, Suite):
+ return suite
+
raise NoSuchSuiteError(suite_id)
def HasSuite(self, suite_id):
"""Check whether or not the database has a suite named 'suite_id'.
*************** class Database(qm.extension.Extension):
*** 724,733 ****
--- 806,823 ----
returns -- A 'ResourceDescriptor' corresponding to 'resource_id'.
raises -- 'NoSuchResourceError' if there is no resource in the
database named 'resource_id'."""
+ if self._is_generic_database:
+ resource = self.GetExtension(resource_id)
+ if isinstance(resource, Resource):
+ return ResourceDescriptor(self,
+ resource_id,
+ resource.GetClassName(),
+ resource.GetExplicitArguments())
+
raise NoSuchResourceError(resource_id)
def HasResource(self, resource_id):
"""Check whether or not the database has a resource named
*************** class Database(qm.extension.Extension):
*** 951,991 ****
# Convert the maps to sequences.
return test_ids.keys(), suite_ids.keys()
- def RemoveExtension(self, id, kind):
- """Remove the extension 'id' from the database.
-
- 'id' -- A label for the 'Extension' instance stored in the
- database.
-
- 'kind' -- The kind of 'Extension' stored with the given 'id'.
- Some databases store different kinds of 'Extension' in different
- namespaces so that it is possible for there to be more than one
- 'Extension' with the same 'id' in a single database."""
-
- raise NotImplementedError
-
-
- def WriteExtension(self, id, extension):
- """Store 'extension' in the database, using the name 'id'.
-
- 'id' -- A label for the 'extension'.
-
- 'extension' -- An instance of 'Extension'.
-
- The 'extension' is stored in the database. If there is a
- previous item in the database with the same id', it is removed
- and replaced with 'extension'. Some databases may not be able
- to store all 'Extension' instances; those database must throw an
- exception when an attempt is made to store such an
- 'extension'."""
-
- raise NotImplementedError
-
-
def IsModifiable(self):
"""Returns true iff this database is modifiable.
returns -- True iff this database is modifiable. If the
database is modifiable, it supports operatings like 'Write'
--- 1041,1050 ----
Index: qm/test/doc/reference.xml
===================================================================
RCS file: /home/qm/Repository/qm/qm/test/doc/reference.xml,v
retrieving revision 1.40
diff -c -5 -p -r1.40 reference.xml
*** qm/test/doc/reference.xml 20 Jul 2005 14:36:11 -0000 1.40
--- qm/test/doc/reference.xml 21 Jul 2005 05:04:48 -0000
***************
*** 2446,2457 ****
server.</para>
<para>A test database class is a Python class that is derived from
&database_class;, which is itself derived from
<classname>Extension</classname>. To create a new database class,
! you must define methods that read and write tests, resources, and
! suites.</para>
<para>The database is also responsible for determining how tests
(and other entities stored in the database) are named. Each item
stored in the database must have a unique name. For a database that
stores files in the filesystem, the name of the file may be a good
--- 2446,2457 ----
server.</para>
<para>A test database class is a Python class that is derived from
&database_class;, which is itself derived from
<classname>Extension</classname>. To create a new database class,
! you must define methods that read and write
! <classname>Extension</classname> instances.</para>
<para>The database is also responsible for determining how tests
(and other entities stored in the database) are named. Each item
stored in the database must have a unique name. For a database that
stores files in the filesystem, the name of the file may be a good
***************
*** 2459,2551 ****
the module might be a good name for the tests. Choosing the naming
convention appropriate requires understanding both the application
domain and the way in which the tests will actually be
stored.</para>
! <para>The database class must have a <function>GetTest</function>
! function which retrieves a test from the database. The
! <parameter>test_id</parameter> parameter provide the name of the
! test. The <function>GetTest</function> function returns a
! &test_descriptor_class;.
! <footnote><para><function>GetTest</function> returns a
! <classname>TestDescriptor</classname>, rather than a
! <classname>Test</classname>, because that allows QMTest to avoid
! loading in the test class. If you are running many tests in
! parallel, on many different machines, this indirection makes QMTest
! more effficient; QMTest only needs to load a particular test class
! on a particular machine if an instance of that class is being run
! on that machine.</para></footnote> A
! <classname>TestDescriptor</classname> indicates the test class, and
! the arguments to that test class. QMTest uses that information to
! instantiate an instance of the test class itself as
! appropriate.</para>
!
! <para>The <function>Write</function> function is the inverse of
! <function>GetTest</function>. The test database is responsible for
! storing the <classname>Test</classname> provided. The name of test
! can be obtained by calling <function>GetId</function> on the
! <classname>Test</classname>. When the <function>Remove</function>
! function is called the database is responsible for removing the test
! named by the <parameter>id</parameter> parameter.</para>
!
! <para>The functions that handle resources are analogous to those
! for tests. For exmaple, <function>GetResource</function> plays the
! same role for resources as <function>GetTest</function> does for
! tests.</para>
!
! </section> <!-- sec-ref-writing-database-classes -->
!
! <section id="sec-registering-extension-class">
! <title>Registering an Extension Class</title>
!
! <para>To use your test or resource class, you must place the Python
! module file containing it in a directory where &qmtest; can find
! it. &qmtest; looks in three places when loading
! extension classes:</para>
!
! <itemizedlist>
! <listitem>
! <para>If the environment variable <envar>QMTEST_CLASS_PATH</envar>
! is defined, &qmtest first checks any directories listed in it.
! This value of this environment variable should be a list of
! directories to check for the module file, in the same format as
! the standard <envar>PATH</envar> environment variable.</para>
! </listitem>
!
! <listitem>
! <para>A test database may specify additional locations to check.</para>
! </listitem>
!
! <listitem>
! <para>QMTest checks the configuration directory (the subdirectory
! named <filename>QMTest</filename> of a test database).</para>
! </listitem>
!
! <listitem>
! <para>Finally, &qmtest; checks a standard directory. This
! directory, installed with &qmtest;, contains modules with the
! standard test classes described in <xref
! linkend="sec-ref-classes"/>.</para>
! </listitem>
! </itemizedlist>
!
! <para>You should generally place module files containing your test
! classes in the test database's <filename>QMTest</filename>
! directory, unless you plan to use the test classes in more than one
! test database.</para>
!
! <para>You must use the <command>&qmtest-cmd; register</command>
! command to register your new extension class. You must perform
! this step no matter where you place the module containing your
! extension class.</para>
!
! <para>You can refer to the new extension class using the syntax
! <classname>module.Class</classname>, where
! <classname>module</classname> is the name of the module and
! <classname>Class</classname> is the name of the class.</para>
! </section> <!-- sec-registering-extension-class -->
</chapter> <!-- chap-test-extension -->
<!--
Local Variables:
--- 2459,2528 ----
the module might be a good name for the tests. Choosing the naming
convention appropriate requires understanding both the application
domain and the way in which the tests will actually be
stored.</para>
! <para>The database class must have a
! <function>GetExtension</function> method which retrieves an instance
! of <classname>Extension</classname> given the name of the instance.
! If your database is modifiable, you must also provide
! <function>WriteExtension</function> and
! <function>RemoveExtension</function> methods. For historical
! reasons, your database class must also set the class variable
! <varname>_is_generic_database</varname> to true.</para> </section>
! <!-- sec-ref-writing-database-classes -->
!
! <section id="sec-registering-extension-class">
! <title>Registering an Extension Class</title>
!
! <para>To use your test or resource class, you must place the Python
! module file containing it in a directory where &qmtest; can find
! it. &qmtest; looks in three places when loading
! extension classes:</para>
!
! <itemizedlist>
! <listitem>
! <para>If the environment variable <envar>QMTEST_CLASS_PATH</envar>
! is defined, &qmtest first checks any directories listed in it.
! This value of this environment variable should be a list of
! directories to check for the module file, in the same format as
! the standard <envar>PATH</envar> environment variable.</para>
! </listitem>
!
! <listitem>
! <para>A test database may specify additional locations to check.</para>
! </listitem>
!
! <listitem>
! <para>QMTest checks the configuration directory (the subdirectory
! named <filename>QMTest</filename> of a test database).</para>
! </listitem>
!
! <listitem>
! <para>Finally, &qmtest; checks a standard directory. This
! directory, installed with &qmtest;, contains modules with the
! standard test classes described in <xref
! linkend="sec-ref-classes"/>.</para>
! </listitem>
! </itemizedlist>
!
! <para>You should generally place module files containing your test
! classes in the test database's <filename>QMTest</filename>
! directory, unless you plan to use the test classes in more than one
! test database.</para>
!
! <para>You must use the <command>&qmtest-cmd; register</command>
! command to register your new extension class. You must perform
! this step no matter where you place the module containing your
! extension class.</para>
!
! <para>You can refer to the new extension class using the syntax
! <classname>module.Class</classname>, where
! <classname>module</classname> is the name of the module and
! <classname>Class</classname> is the name of the class.</para>
! </section> <!-- sec-registering-extension-class -->
</chapter> <!-- chap-test-extension -->
<!--
Local Variables:
More information about the qmtest
mailing list