Przeglądaj źródła

Fixed #12991 -- Added unittest2 support. Thanks to PaulM for the draft patch, and to Luke, Karen, Justin, Alex, Łukasz Rekucki, and Chuck Harmston for their help testing and reviewing the final patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14139 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Russell Keith-Magee 14 lat temu
rodzic
commit
121d2e3678
100 zmienionych plików z 3866 dodań i 1042 usunięć
  1. 3 2
      django/contrib/admindocs/tests/__init__.py
  2. 3 3
      django/contrib/formtools/tests.py
  3. 2 2
      django/contrib/gis/db/backends/spatialite/creation.py
  4. 1 1
      django/contrib/gis/gdal/tests/__init__.py
  5. 42 41
      django/contrib/gis/gdal/tests/test_envelope.py
  6. 2 1
      django/contrib/gis/gdal/tests/test_geom.py
  7. 4 3
      django/contrib/gis/gdal/tests/test_srs.py
  8. 1 1
      django/contrib/gis/geos/tests/__init__.py
  9. 1 1
      django/contrib/gis/geos/tests/test_geos_mutation.py
  10. 2 1
      django/contrib/gis/geos/tests/test_mutable_list.py
  11. 2 1
      django/contrib/gis/tests/__init__.py
  12. 3 1
      django/contrib/gis/tests/geoapp/test_feeds.py
  13. 9 5
      django/contrib/gis/tests/geoapp/test_sitemaps.py
  14. 17 17
      django/contrib/gis/tests/layermap/tests.py
  15. 2 2
      django/contrib/gis/tests/test_geoforms.py
  16. 27 26
      django/contrib/gis/tests/test_measure.py
  17. 4 4
      django/contrib/gis/tests/test_spatialrefsys.py
  18. 61 48
      django/contrib/markup/tests.py
  19. 1 1
      django/contrib/messages/tests/middleware.py
  20. 4 3
      django/contrib/sessions/tests.py
  21. 90 0
      django/db/backends/__init__.py
  22. 3 14
      django/db/backends/creation.py
  23. 1 1
      django/db/backends/dummy/base.py
  24. 10 2
      django/db/backends/mysql/base.py
  25. 5 3
      django/db/backends/oracle/base.py
  26. 4 1
      django/db/backends/postgresql/base.py
  27. 4 1
      django/db/backends/postgresql_psycopg2/base.py
  28. 23 2
      django/db/backends/sqlite3/base.py
  29. 1 1
      django/test/__init__.py
  30. 11 48
      django/test/simple.py
  31. 28 4
      django/test/testcases.py
  32. 80 0
      django/utils/unittest/__init__.py
  33. 10 0
      django/utils/unittest/__main__.py
  34. 1083 0
      django/utils/unittest/case.py
  35. 9 0
      django/utils/unittest/collector.py
  36. 64 0
      django/utils/unittest/compatibility.py
  37. 322 0
      django/utils/unittest/loader.py
  38. 241 0
      django/utils/unittest/main.py
  39. 183 0
      django/utils/unittest/result.py
  40. 206 0
      django/utils/unittest/runner.py
  41. 57 0
      django/utils/unittest/signals.py
  42. 287 0
      django/utils/unittest/suite.py
  43. 99 0
      django/utils/unittest/util.py
  44. 28 0
      docs/releases/1.3.txt
  45. 51 19
      docs/topics/testing.txt
  46. 3 6
      tests/modeltests/basic/models.py
  47. 10 11
      tests/modeltests/custom_pk/tests.py
  48. 45 44
      tests/modeltests/fixtures/tests.py
  49. 9 8
      tests/modeltests/lookup/models.py
  50. 1 1
      tests/modeltests/test_client/tests.py
  51. 152 148
      tests/modeltests/transactions/tests.py
  52. 1 1
      tests/modeltests/validation/__init__.py
  53. 3 1
      tests/modeltests/validation/test_unique.py
  54. 2 1
      tests/modeltests/validation/validators.py
  55. 3 1
      tests/modeltests/validators/tests.py
  56. 1 1
      tests/regressiontests/admin_scripts/tests.py
  57. 5 6
      tests/regressiontests/admin_util/tests.py
  58. 34 32
      tests/regressiontests/admin_views/tests.py
  59. 3 2
      tests/regressiontests/admin_widgets/tests.py
  60. 83 102
      tests/regressiontests/aggregation_regress/tests.py
  61. 1 1
      tests/regressiontests/app_loading/tests.py
  62. 1 1
      tests/regressiontests/backends/models.py
  63. 83 83
      tests/regressiontests/backends/tests.py
  64. 1 1
      tests/regressiontests/bash_completion/tests.py
  65. 2 1
      tests/regressiontests/bug639/tests.py
  66. 1 2
      tests/regressiontests/bug8245/tests.py
  67. 2 1
      tests/regressiontests/builtin_server/tests.py
  68. 2 1
      tests/regressiontests/cache/tests.py
  69. 9 8
      tests/regressiontests/datatypes/tests.py
  70. 10 9
      tests/regressiontests/decorators/tests.py
  71. 54 53
      tests/regressiontests/delete_regress/tests.py
  72. 9 8
      tests/regressiontests/dispatch/tests/test_dispatcher.py
  73. 1 1
      tests/regressiontests/dispatch/tests/test_saferef.py
  74. 14 14
      tests/regressiontests/expressions_regress/tests.py
  75. 69 66
      tests/regressiontests/file_storage/tests.py
  76. 4 4
      tests/regressiontests/file_uploads/tests.py
  77. 2 2
      tests/regressiontests/fixtures_regress/models.py
  78. 1 2
      tests/regressiontests/forms/fields.py
  79. 1 1
      tests/regressiontests/forms/input_formats.py
  80. 1 2
      tests/regressiontests/forms/validators.py
  81. 2 3
      tests/regressiontests/forms/widgets.py
  82. 25 24
      tests/regressiontests/httpwrappers/tests.py
  83. 2 1
      tests/regressiontests/humanize/tests.py
  84. 8 8
      tests/regressiontests/introspection/tests.py
  85. 1 1
      tests/regressiontests/localflavor/tests.py
  86. 5 5
      tests/regressiontests/max_lengths/tests.py
  87. 11 11
      tests/regressiontests/model_fields/tests.py
  88. 2 4
      tests/regressiontests/model_regress/models.py
  89. 1 2
      tests/regressiontests/pagination_regress/tests.py
  90. 4 4
      tests/regressiontests/queries/models.py
  91. 1 2
      tests/regressiontests/queries/tests.py
  92. 3 3
      tests/regressiontests/serializers_regress/tests.py
  93. 1 1
      tests/regressiontests/settings_tests/tests.py
  94. 3 3
      tests/regressiontests/templates/loaders.py
  95. 1 1
      tests/regressiontests/templates/nodelist.py
  96. 1 1
      tests/regressiontests/templates/smartif.py
  97. 30 30
      tests/regressiontests/templates/tests.py
  98. 48 48
      tests/regressiontests/test_client_regress/models.py
  99. 2 2
      tests/regressiontests/test_runner/tests.py
  100. 1 2
      tests/regressiontests/urlpatterns_reverse/tests.py

+ 3 - 2
django/contrib/admindocs/tests/__init__.py

@@ -1,7 +1,8 @@
-import unittest
-import fields
 from django.contrib.admindocs import views
 from django.db.models import fields as builtin_fields
+from django.utils import unittest
+
+import fields
 
 
 class TestFieldType(unittest.TestCase):

+ 3 - 3
django/contrib/formtools/tests.py

@@ -1,8 +1,8 @@
-import unittest
 from django import forms
-from django.contrib.formtools import preview, wizard, utils
 from django import http
+from django.contrib.formtools import preview, wizard, utils
 from django.test import TestCase
+from django.utils import unittest
 
 success_string = "Done was called!"
 
@@ -115,7 +115,7 @@ class SecurityHashTests(unittest.TestCase):
         hash1 = utils.security_hash(None, f1)
         hash2 = utils.security_hash(None, f2)
         self.assertEqual(hash1, hash2)
-        
+
     def test_empty_permitted(self):
         """
         Regression test for #10643: the security hash should allow forms with

+ 2 - 2
django/contrib/gis/db/backends/spatialite/creation.py

@@ -22,8 +22,8 @@ class SpatiaLiteCreation(DatabaseCreation):
         self.connection.close()
 
         self.connection.settings_dict["NAME"] = test_database_name
-        can_rollback = self._rollback_works()
-        self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
+        # Confirm the feature set of the test database
+        self.connection.features.confirm()
         # Need to load the SpatiaLite initialization SQL before running `syncdb`.
         self.load_spatialite_sql()
         call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)

+ 1 - 1
django/contrib/gis/gdal/tests/__init__.py

@@ -2,7 +2,7 @@
 Module for executing all of the GDAL tests.  None
 of these tests require the use of the database.
 """
-from unittest import TestSuite, TextTestRunner
+from django.utils.unittest import TestSuite, TextTestRunner
 
 # Importing the GDAL test modules.
 import test_driver, test_ds, test_envelope, test_geom, test_srs

+ 42 - 41
django/contrib/gis/gdal/tests/test_envelope.py

@@ -1,5 +1,6 @@
-import unittest
 from django.contrib.gis.gdal import Envelope, OGRException
+from django.utils import unittest
+
 
 class TestPoint(object):
     def __init__(self, x, y):
@@ -8,8 +9,8 @@ class TestPoint(object):
 
 class EnvelopeTest(unittest.TestCase):
 
-    def setUp(self): 
-        self.e = Envelope(0, 0, 5, 5) 
+    def setUp(self):
+        self.e = Envelope(0, 0, 5, 5)
 
     def test01_init(self):
         "Testing Envelope initilization."
@@ -23,10 +24,10 @@ class EnvelopeTest(unittest.TestCase):
         self.assertRaises(OGRException, Envelope, ())
         self.assertRaises(ValueError, Envelope, 0, 'a', 5, 5)
         self.assertRaises(TypeError, Envelope, u'foo')
-        self.assertRaises(OGRException, Envelope, (1, 1, 0, 0)) 
-        try: 
-            Envelope(0, 0, 0, 0) 
-        except OGRException: 
+        self.assertRaises(OGRException, Envelope, (1, 1, 0, 0))
+        try:
+            Envelope(0, 0, 0, 0)
+        except OGRException:
             self.fail("shouldn't raise an exception for min_x == max_x or min_y == max_y")
 
     def test02_properties(self):
@@ -49,41 +50,41 @@ class EnvelopeTest(unittest.TestCase):
         self.assertEqual(e1, e2)
         self.assertEqual((0.523, 0.217, 253.23, 523.69), e1)
 
-    def test04_expand_to_include_pt_2_params(self): 
-        "Testing Envelope expand_to_include -- point as two parameters." 
-        self.e.expand_to_include(2, 6) 
-        self.assertEqual((0, 0, 5, 6), self.e) 
-        self.e.expand_to_include(-1, -1) 
-        self.assertEqual((-1, -1, 5, 6), self.e) 
-  
-    def test05_expand_to_include_pt_2_tuple(self): 
-        "Testing Envelope expand_to_include -- point as a single 2-tuple parameter." 
-        self.e.expand_to_include((10, 10)) 
-        self.assertEqual((0, 0, 10, 10), self.e) 
-        self.e.expand_to_include((-10, -10)) 
-        self.assertEqual((-10, -10, 10, 10), self.e) 
+    def test04_expand_to_include_pt_2_params(self):
+        "Testing Envelope expand_to_include -- point as two parameters."
+        self.e.expand_to_include(2, 6)
+        self.assertEqual((0, 0, 5, 6), self.e)
+        self.e.expand_to_include(-1, -1)
+        self.assertEqual((-1, -1, 5, 6), self.e)
+
+    def test05_expand_to_include_pt_2_tuple(self):
+        "Testing Envelope expand_to_include -- point as a single 2-tuple parameter."
+        self.e.expand_to_include((10, 10))
+        self.assertEqual((0, 0, 10, 10), self.e)
+        self.e.expand_to_include((-10, -10))
+        self.assertEqual((-10, -10, 10, 10), self.e)
+
+    def test06_expand_to_include_extent_4_params(self):
+        "Testing Envelope expand_to_include -- extent as 4 parameters."
+        self.e.expand_to_include(-1, 1, 3, 7)
+        self.assertEqual((-1, 0, 5, 7), self.e)
+
+    def test06_expand_to_include_extent_4_tuple(self):
+        "Testing Envelope expand_to_include -- extent as a single 4-tuple parameter."
+        self.e.expand_to_include((-1, 1, 3, 7))
+        self.assertEqual((-1, 0, 5, 7), self.e)
+
+    def test07_expand_to_include_envelope(self):
+        "Testing Envelope expand_to_include with Envelope as parameter."
+        self.e.expand_to_include(Envelope(-1, 1, 3, 7))
+        self.assertEqual((-1, 0, 5, 7), self.e)
 
-    def test06_expand_to_include_extent_4_params(self): 
-        "Testing Envelope expand_to_include -- extent as 4 parameters." 
-        self.e.expand_to_include(-1, 1, 3, 7) 
-        self.assertEqual((-1, 0, 5, 7), self.e) 
-  
-    def test06_expand_to_include_extent_4_tuple(self): 
-        "Testing Envelope expand_to_include -- extent as a single 4-tuple parameter." 
-        self.e.expand_to_include((-1, 1, 3, 7)) 
-        self.assertEqual((-1, 0, 5, 7), self.e) 
-  
-    def test07_expand_to_include_envelope(self): 
-        "Testing Envelope expand_to_include with Envelope as parameter." 
-        self.e.expand_to_include(Envelope(-1, 1, 3, 7)) 
-        self.assertEqual((-1, 0, 5, 7), self.e) 
-  
-    def test08_expand_to_include_point(self): 
-        "Testing Envelope expand_to_include with Point as parameter." 
-        self.e.expand_to_include(TestPoint(-1, 1)) 
-        self.assertEqual((-1, 0, 5, 5), self.e) 
-        self.e.expand_to_include(TestPoint(10, 10)) 
-        self.assertEqual((-1, 0, 10, 10), self.e) 
+    def test08_expand_to_include_point(self):
+        "Testing Envelope expand_to_include with Point as parameter."
+        self.e.expand_to_include(TestPoint(-1, 1))
+        self.assertEqual((-1, 0, 5, 5), self.e)
+        self.e.expand_to_include(TestPoint(10, 10))
+        self.assertEqual((-1, 0, 10, 10), self.e)
 
 def suite():
     s = unittest.TestSuite()

+ 2 - 1
django/contrib/gis/gdal/tests/test_geom.py

@@ -1,8 +1,9 @@
-import unittest
 from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, \
     OGRException, OGRIndexError, SpatialReference, CoordTransform, \
     gdal_version
 from django.contrib.gis.tests.geometries import *
+from django.utils import unittest
+
 
 class OGRGeomTest(unittest.TestCase):
     "This tests the OGR Geometry."

+ 4 - 3
django/contrib/gis/gdal/tests/test_srs.py

@@ -1,5 +1,6 @@
-import unittest
 from django.contrib.gis.gdal import SpatialReference, CoordTransform, OGRException, SRSException
+from django.utils import unittest
+
 
 class TestSRS:
     def __init__(self, wkt, **kwargs):
@@ -79,7 +80,7 @@ class SpatialRefTest(unittest.TestCase):
                 srs1 = SpatialReference(s.wkt)
                 srs2 = SpatialReference(s.proj)
                 self.assertEqual(srs1.proj, srs2.proj)
-        
+
     def test05_epsg(self):
         "Test EPSG import."
         for s in srlist:
@@ -159,7 +160,7 @@ class SpatialRefTest(unittest.TestCase):
         self.assertEqual(4326, int(s1['AUTHORITY', 1]))
         #for i in range(7): self.assertEqual(0, int(s1['TOWGS84', i]))
         self.assertEqual(None, s1['FOOBAR'])
-    
+
 def suite():
     s = unittest.TestSuite()
     s.addTest(unittest.makeSuite(SpatialRefTest))

+ 1 - 1
django/contrib/gis/geos/tests/__init__.py

@@ -1,7 +1,7 @@
 """
 GEOS Testing module.
 """
-from unittest import TestSuite, TextTestRunner
+from django.utils.unittest import TestSuite, TextTestRunner
 import test_geos, test_io, test_geos_mutation, test_mutable_list
 
 test_suites = [

+ 1 - 1
django/contrib/gis/geos/tests/test_geos_mutation.py

@@ -1,12 +1,12 @@
 # Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
 # Modified from original contribution by Aryeh Leib Taurog, which was
 # released under the New BSD license.
-import unittest
 
 import django.utils.copycompat as copy
 
 from django.contrib.gis.geos import *
 from django.contrib.gis.geos.error import GEOSIndexError
+from django.utils import unittest
 
 def getItem(o,i): return o[i]
 def delItem(o,i): del o[i]

+ 2 - 1
django/contrib/gis/geos/tests/test_mutable_list.py

@@ -3,8 +3,9 @@
 #
 # Modified from original contribution by Aryeh Leib Taurog, which was
 # released under the New BSD license.
-import unittest
 from django.contrib.gis.geos.mutable_list import ListMixin
+from django.utils import unittest
+
 
 class UserListA(ListMixin):
     _mytype = tuple

+ 2 - 1
django/contrib/gis/tests/__init__.py

@@ -1,9 +1,10 @@
 import sys
-import unittest
 
 from django.conf import settings
 from django.db.models import get_app
 from django.test.simple import build_suite, DjangoTestSuiteRunner
+from django.utils import unittest
+
 
 def run_tests(*args, **kwargs):
     from django.test.simple import run_tests as base_run_tests

+ 3 - 1
django/contrib/gis/tests/geoapp/test_feeds.py

@@ -1,9 +1,11 @@
-import unittest
 from xml.dom import minidom
 
 from django.test import Client
+from django.utils import unittest
+
 from models import City
 
+
 class GeoFeedTest(unittest.TestCase):
     client = Client()
 

+ 9 - 5
django/contrib/gis/tests/geoapp/test_sitemaps.py

@@ -1,9 +1,13 @@
-import unittest, zipfile, cStringIO
+import cStringIO
 from xml.dom import minidom
+import zipfile
 
 from django.test import Client
+from django.utils import unittest
+
 from models import City, Country
 
+
 class GeoSitemapTest(unittest.TestCase):
     client = Client()
 
@@ -30,7 +34,7 @@ class GeoSitemapTest(unittest.TestCase):
             urlset = doc.firstChild
             self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9')
             self.assertEqual(urlset.getAttribute(u'xmlns:geo'), u'http://www.google.com/geo/schemas/sitemap/1.0')
-        
+
             urls = urlset.getElementsByTagName('url')
             self.assertEqual(2, len(urls)) # Should only be 2 sitemaps.
             for url in urls:
@@ -42,7 +46,7 @@ class GeoSitemapTest(unittest.TestCase):
 
                 # Getting the relative URL since we don't have a real site.
                 kml_url = url.getElementsByTagName('loc')[0].childNodes[0].data.split('http://example.com')[1]
-                
+
                 if kml_type == 'kml':
                     kml_doc = minidom.parseString(self.client.get(kml_url).content)
                 elif kml_type == 'kmz':
@@ -52,7 +56,7 @@ class GeoSitemapTest(unittest.TestCase):
                     self.assertEqual(1, len(zf.filelist))
                     self.assertEqual('doc.kml', zf.filelist[0].filename)
                     kml_doc = minidom.parseString(zf.read('doc.kml'))
-                
+
                 # Ensuring the correct number of placemarks are in the KML doc.
                 if 'city' in kml_url:
                     model = City
@@ -65,7 +69,7 @@ class GeoSitemapTest(unittest.TestCase):
         from feeds import feed_dict
 
         doc = minidom.parseString(self.client.get('/geoapp/sitemaps/georss.xml').content)
-   
+
         # Ensuring the right sitemaps namespaces are present.
         urlset = doc.firstChild
         self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9')

+ 17 - 17
django/contrib/gis/tests/layermap/tests.py

@@ -1,7 +1,7 @@
 import os
-import unittest
 from decimal import Decimal
 
+from django.utils import unittest
 from django.utils.copycompat import copy
 
 from django.contrib.gis.gdal import DataSource
@@ -15,9 +15,9 @@ city_shp = os.path.join(shp_path, 'cities', 'cities.shp')
 co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
 inter_shp = os.path.join(shp_path, 'interstates', 'interstates.shp')
 
-# Dictionaries to hold what's expected in the county shapefile.  
+# Dictionaries to hold what's expected in the county shapefile.
 NAMES  = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
-NUMS   = [1, 2, 1, 19, 1] # Number of polygons for each.                                                                                                                                                  
+NUMS   = [1, 2, 1, 19, 1] # Number of polygons for each.
 STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
 
 class LayerMapTest(unittest.TestCase):
@@ -98,7 +98,7 @@ class LayerMapTest(unittest.TestCase):
 
         # Two interstate should have imported correctly.
         self.assertEqual(2, Interstate.objects.count())
-        
+
         # Verifying the values in the layer w/the model.
         ds = DataSource(inter_shp)
 
@@ -106,7 +106,7 @@ class LayerMapTest(unittest.TestCase):
         valid_feats = ds[0][:2]
         for feat in valid_feats:
             istate = Interstate.objects.get(name=feat['Name'].value)
-            
+
             if feat.fid == 0:
                 self.assertEqual(Decimal(str(feat['Length'])), istate.length)
             elif feat.fid == 1:
@@ -125,7 +125,7 @@ class LayerMapTest(unittest.TestCase):
             c = County.objects.get(name=name)
             self.assertEqual(n, len(c.mpoly))
             self.assertEqual(st, c.state.name) # Checking ForeignKey mapping.
-            
+
             # Multiple records because `unique` was not set.
             if county_feat:
                 qs = CountyFeat.objects.filter(name=name)
@@ -137,7 +137,7 @@ class LayerMapTest(unittest.TestCase):
         try:
             # Telling LayerMapping that we want no transformations performed on the data.
             lm = LayerMapping(County, co_shp, co_mapping, transform=False)
-        
+
             # Specifying the source spatial reference system via the `source_srs` keyword.
             lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269)
             lm = LayerMapping(County, co_shp, co_mapping, source_srs='NAD83')
@@ -147,7 +147,7 @@ class LayerMapTest(unittest.TestCase):
                 lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg)
         except:
             self.fail('No exception should be raised for proper use of keywords.')
-            
+
         # Testing invalid params for the `unique` keyword.
         for e, arg in ((TypeError, 5.0), (ValueError, 'foobar'), (ValueError, ('name', 'mpolygon'))):
             self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg)
@@ -177,11 +177,11 @@ class LayerMapTest(unittest.TestCase):
         # are not collections will be converted into them.  For example,
         # a Point column would be converted to MultiPoint. Other things being done
         # w/the keyword args:
-        #  `transform=False`: Specifies that no transform is to be done; this 
+        #  `transform=False`: Specifies that no transform is to be done; this
         #    has the effect of ignoring the spatial reference check (because the
         #    county shapefile does not have implicit spatial reference info).
-        # 
-        #  `unique='name'`: Creates models on the condition that they have 
+        #
+        #  `unique='name'`: Creates models on the condition that they have
         #    unique county names; geometries from each feature however will be
         #    appended to the geometry collection of the unique model.  Thus,
         #    all of the various islands in Honolulu county will be in in one
@@ -201,7 +201,7 @@ class LayerMapTest(unittest.TestCase):
         "Tests the `fid_range` keyword and the `step` keyword of .save()."
         # Function for clearing out all the counties before testing.
         def clear_counties(): County.objects.all().delete()
-        
+
         # Initializing the LayerMapping object to use in these tests.
         lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
 
@@ -213,9 +213,9 @@ class LayerMapTest(unittest.TestCase):
 
         # Step keyword should not be allowed w/`fid_range`.
         fr = (3, 5) # layer[3:5]
-        self.assertRaises(LayerMapError, lm.save, fid_range=fr, step=10) 
+        self.assertRaises(LayerMapError, lm.save, fid_range=fr, step=10)
         lm.save(fid_range=fr)
-        
+
         # Features IDs 3 & 4 are for Galveston County, Texas -- only
         # one model is returned because the `unique` keyword was set.
         qs = County.objects.all()
@@ -229,9 +229,9 @@ class LayerMapTest(unittest.TestCase):
         lm.save(fid_range=slice(None, 1), silent=True, strict=True) # layer[:1]
 
         # Only Pueblo & Honolulu counties should be present because of
-        # the `unique` keyword.  Have to set `order_by` on this QuerySet 
+        # the `unique` keyword.  Have to set `order_by` on this QuerySet
         # or else MySQL will return a different ordering than the other dbs.
-        qs = County.objects.order_by('name') 
+        qs = County.objects.order_by('name')
         self.assertEqual(2, qs.count())
         hi, co = tuple(qs)
         hi_idx, co_idx = tuple(map(NAMES.index, ('Honolulu', 'Pueblo')))
@@ -265,7 +265,7 @@ class LayerMapTest(unittest.TestCase):
 
         self.assertEqual(6, ICity1.objects.count())
         self.assertEqual(3, ICity2.objects.count())
-        
+
 def suite():
     s = unittest.TestSuite()
     s.addTest(unittest.makeSuite(LayerMapTest))

+ 2 - 2
django/contrib/gis/tests/test_geoforms.py

@@ -1,8 +1,8 @@
-import unittest
-
 from django.forms import ValidationError
 from django.contrib.gis import forms
 from django.contrib.gis.geos import GEOSGeometry
+from django.utils import unittest
+
 
 class GeometryFieldTest(unittest.TestCase):
 

+ 27 - 26
django/contrib/gis/tests/test_measure.py

@@ -1,10 +1,11 @@
 """
-Distance and Area objects to allow for sensible and convienient calculation 
+Distance and Area objects to allow for sensible and convienient calculation
 and conversions. Here are some tests.
 """
 
-import unittest
 from django.contrib.gis.measure import Distance, Area, D, A
+from django.utils import unittest
+
 
 class DistanceTest(unittest.TestCase):
     "Testing the Distance object"
@@ -30,7 +31,7 @@ class DistanceTest(unittest.TestCase):
             self.assertEqual(d.m, 1.0)
             self.assertEqual(d.mm, 1000.0)
 
-    
+
     def testInitInvalid(self):
         "Testing initialisation from invalid units"
         self.assertRaises(AttributeError, D, banana=100)
@@ -40,7 +41,7 @@ class DistanceTest(unittest.TestCase):
         d = D(m=100)
         self.assertEqual(d.km, 0.1)
         self.assertAlmostEqual(d.ft, 328.084, 3)
-    
+
     def testAccessInvalid(self):
         "Testing access in invalid units"
         d = D(m=100)
@@ -55,12 +56,12 @@ class DistanceTest(unittest.TestCase):
         self.assertEqual(d3.m, 300)
         d3 += d1
         self.assertEqual(d3.m, 400)
-        
+
         d4 = d1 - d2
         self.assertEqual(d4.m, -100)
         d4 -= d1
         self.assertEqual(d4.m, -200)
-        
+
         try:
             d5 = d1 + 1
         except TypeError, e:
@@ -88,7 +89,7 @@ class DistanceTest(unittest.TestCase):
             pass
         else:
             self.fail('Distance -= number should raise TypeError')
-            
+
     def testMultiplication(self):
         "Test multiplication & division"
         d1 = D(m=100)
@@ -99,12 +100,12 @@ class DistanceTest(unittest.TestCase):
         self.assertEqual(d3.m, 200)
         d3 *= 5
         self.assertEqual(d3.m, 1000)
-        
+
         d4 = d1 / 2
         self.assertEqual(d4.m, 50)
         d4 /= 5
         self.assertEqual(d4.m, 10)
-        
+
         a5 = d1 * D(m=10)
         self.assert_(isinstance(a5, Area))
         self.assertEqual(a5.sq_m, 100*10)
@@ -115,7 +116,7 @@ class DistanceTest(unittest.TestCase):
             pass
         else:
             self.fail('Distance *= Distance should raise TypeError')
-            
+
         try:
             d5 = d1 / D(m=1)
         except TypeError, e:
@@ -143,23 +144,23 @@ class DistanceTest(unittest.TestCase):
         self.assertEqual(d5._default_unit, 'm')
         d6 = d1 / 2
         self.assertEqual(d6._default_unit, 'm')
-    
+
     def testComparisons(self):
         "Testing comparisons"
         d1 = D(m=100)
         d2 = D(km=1)
         d3 = D(km=0)
-        
+
         self.assert_(d2 > d1)
         self.assert_(d1 == d1)
         self.assert_(d1 < d2)
         self.failIf(d3)
-        
+
     def testUnitsStr(self):
         "Testing conversion to strings"
         d1 = D(m=100)
         d2 = D(km=3.5)
-        
+
         self.assertEqual(str(d1), '100.0 m')
         self.assertEqual(str(d2), '3.5 km')
         self.assertEqual(repr(d1), 'Distance(m=100.0)')
@@ -185,7 +186,7 @@ class AreaTest(unittest.TestCase):
 
         a = A(sq_mi=100)
         self.assertEqual(a.sq_m, 258998811.0336)
-    
+
     def testInitInvaliA(self):
         "Testing initialisation from invalid units"
         self.assertRaises(AttributeError, A, banana=100)
@@ -195,7 +196,7 @@ class AreaTest(unittest.TestCase):
         a = A(sq_m=100)
         self.assertEqual(a.sq_km, 0.0001)
         self.assertAlmostEqual(a.sq_ft, 1076.391, 3)
-    
+
     def testAccessInvaliA(self):
         "Testing access in invalid units"
         a = A(sq_m=100)
@@ -210,12 +211,12 @@ class AreaTest(unittest.TestCase):
         self.assertEqual(a3.sq_m, 300)
         a3 += a1
         self.assertEqual(a3.sq_m, 400)
-        
+
         a4 = a1 - a2
         self.assertEqual(a4.sq_m, -100)
         a4 -= a1
         self.assertEqual(a4.sq_m, -200)
-        
+
         try:
             a5 = a1 + 1
         except TypeError, e:
@@ -243,7 +244,7 @@ class AreaTest(unittest.TestCase):
             pass
         else:
             self.fail('Area -= number should raise TypeError')
-            
+
     def testMultiplication(self):
         "Test multiplication & division"
         a1 = A(sq_m=100)
@@ -254,12 +255,12 @@ class AreaTest(unittest.TestCase):
         self.assertEqual(a3.sq_m, 200)
         a3 *= 5
         self.assertEqual(a3.sq_m, 1000)
-        
+
         a4 = a1 / 2
         self.assertEqual(a4.sq_m, 50)
         a4 /= 5
         self.assertEqual(a4.sq_m, 10)
-        
+
         try:
             a5 = a1 * A(sq_m=1)
         except TypeError, e:
@@ -273,7 +274,7 @@ class AreaTest(unittest.TestCase):
             pass
         else:
             self.fail('Area *= Area should raise TypeError')
-            
+
         try:
             a5 = a1 / A(sq_m=1)
         except TypeError, e:
@@ -301,23 +302,23 @@ class AreaTest(unittest.TestCase):
         self.assertEqual(a5._default_unit, 'sq_m')
         a6 = a1 / 2
         self.assertEqual(a6._default_unit, 'sq_m')
-    
+
     def testComparisons(self):
         "Testing comparisons"
         a1 = A(sq_m=100)
         a2 = A(sq_km=1)
         a3 = A(sq_km=0)
-        
+
         self.assert_(a2 > a1)
         self.assert_(a1 == a1)
         self.assert_(a1 < a2)
         self.failIf(a3)
-        
+
     def testUnitsStr(self):
         "Testing conversion to strings"
         a1 = A(sq_m=100)
         a2 = A(sq_km=3.5)
-        
+
         self.assertEqual(str(a1), '100.0 sq_m')
         self.assertEqual(str(a2), '3.5 sq_km')
         self.assertEqual(repr(a1), 'Area(sq_m=100.0)')

+ 4 - 4
django/contrib/gis/tests/test_spatialrefsys.py

@@ -1,7 +1,7 @@
-import unittest
-
 from django.db import connection
 from django.contrib.gis.tests.utils import mysql, no_mysql, oracle, postgis, spatialite
+from django.utils import unittest
+
 
 test_srs = ({'srid' : 4326,
              'auth_name' : ('EPSG', True),
@@ -9,7 +9,7 @@ test_srs = ({'srid' : 4326,
              'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]',
              'srtext14' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]',
              'proj4' : '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ',
-             'spheroid' : 'WGS 84', 'name' : 'WGS 84', 
+             'spheroid' : 'WGS 84', 'name' : 'WGS 84',
              'geographic' : True, 'projected' : False, 'spatialite' : True,
              'ellipsoid' : (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
              'eprec' : (1, 1, 9),
@@ -49,7 +49,7 @@ class SpatialRefSysTest(unittest.TestCase):
             auth_name, oracle_flag = sd['auth_name']
             if postgis or (oracle and oracle_flag):
                 self.assertEqual(True, srs.auth_name.startswith(auth_name))
-                
+
             self.assertEqual(sd['auth_srid'], srs.auth_srid)
 
             # No proj.4 and different srtext on oracle backends :(

+ 61 - 48
django/contrib/markup/tests.py

@@ -1,77 +1,90 @@
 # Quick tests for the markup templatetags (django.contrib.markup)
-
 import re
-import unittest
 
 from django.template import Template, Context, add_to_builtins
+from django.utils import unittest
 from django.utils.html import escape
 
 add_to_builtins('django.contrib.markup.templatetags.markup')
 
+try:
+    import textile
+except ImportError:
+    textile = None
+
+try:
+    import markdown
+except ImportError:
+    markdown = None
+
+try:
+    import docutils
+except ImportError:
+    docutils = None
+
 class Templates(unittest.TestCase):
-    def test_textile(self):
-        try:
-            import textile
-        except ImportError:
-            textile = None
 
-        textile_content = """Paragraph 1
+    textile_content = """Paragraph 1
 
 Paragraph 2 with "quotes" and @code@"""
 
-        t = Template("{{ textile_content|textile }}")
-        rendered = t.render(Context(locals())).strip()
-        if textile:
-            self.assertEqual(rendered.replace('\t', ''), """<p>Paragraph 1</p>
+    markdown_content = """Paragraph 1
 
-<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
-        else:
-            self.assertEqual(rendered, escape(textile_content))
+## An h2"""
 
-    def test_markdown(self):
-        try:
-            import markdown
-        except ImportError:
-            markdown = None
+    rest_content = """Paragraph 1
 
-        markdown_content = """Paragraph 1
+Paragraph 2 with a link_
 
-## An h2"""
+.. _link: http://www.example.com/"""
 
-        t = Template("{{ markdown_content|markdown }}")
-        rendered = t.render(Context(locals())).strip()
-        if markdown:
-            pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""")
-            self.assert_(pattern.match(rendered))
-        else:
-            self.assertEqual(rendered, markdown_content)
 
-    def test_docutils(self):
-        try:
-            import docutils
-        except ImportError:
-            docutils = None
+    @unittest.skipUnless(textile, 'texttile not installed')
+    def test_textile(self):
+        t = Template("{{ textile_content|textile }}")
+        rendered = t.render(Context({'textile_content':self.textile_content})).strip()
+        self.assertEqual(rendered.replace('\t', ''), """<p>Paragraph 1</p>
 
-        rest_content = """Paragraph 1
+<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
 
-Paragraph 2 with a link_
+    @unittest.skipIf(textile, 'texttile is installed')
+    def test_no_textile(self):
+        t = Template("{{ textile_content|textile }}")
+        rendered = t.render(Context({'textile_content':self.textile_content})).strip()
+        self.assertEqual(rendered, escape(self.textile_content))
 
-.. _link: http://www.example.com/"""
+    @unittest.skipUnless(markdown, 'markdown not installed')
+    def test_markdown(self):
+        t = Template("{{ markdown_content|markdown }}")
+        rendered = t.render(Context({'markdown_content':self.markdown_content})).strip()
+        pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""")
+        self.assertTrue(pattern.match(rendered))
+
+    @unittest.skipIf(markdown, 'markdown is installed')
+    def test_no_markdown(self):
+        t = Template("{{ markdown_content|markdown }}")
+        rendered = t.render(Context({'markdown_content':self.markdown_content})).strip()
+        self.assertEqual(rendered, self.markdown_content)
 
+    @unittest.skipUnless(docutils, 'docutils not installed')
+    def test_docutils(self):
         t = Template("{{ rest_content|restructuredtext }}")
-        rendered = t.render(Context(locals())).strip()
-        if docutils:
-            # Different versions of docutils return slightly different HTML
-            try:
-                # Docutils v0.4 and earlier
-                self.assertEqual(rendered, """<p>Paragraph 1</p>
+        rendered = t.render(Context({'rest_content':self.rest_content})).strip()
+        # Different versions of docutils return slightly different HTML
+        try:
+            # Docutils v0.4 and earlier
+            self.assertEqual(rendered, """<p>Paragraph 1</p>
 <p>Paragraph 2 with a <a class="reference" href="http://www.example.com/">link</a></p>""")
-            except AssertionError, e:
-                # Docutils from SVN (which will become 0.5)
-                self.assertEqual(rendered, """<p>Paragraph 1</p>
+        except AssertionError, e:
+            # Docutils from SVN (which will become 0.5)
+            self.assertEqual(rendered, """<p>Paragraph 1</p>
 <p>Paragraph 2 with a <a class="reference external" href="http://www.example.com/">link</a></p>""")
-        else:
-            self.assertEqual(rendered, rest_content)
+
+    @unittest.skipIf(docutils, 'docutils is installed')
+    def test_no_docutils(self):
+        t = Template("{{ rest_content|restructuredtext }}")
+        rendered = t.render(Context({'rest_content':self.rest_content})).strip()
+        self.assertEqual(rendered, self.rest_content)
 
 
 if __name__ == '__main__':

+ 1 - 1
django/contrib/messages/tests/middleware.py

@@ -1,6 +1,6 @@
-import unittest
 from django import http
 from django.contrib.messages.middleware import MessageMiddleware
+from django.utils import unittest
 
 
 class MiddlewareTest(unittest.TestCase):

+ 4 - 3
django/contrib/sessions/tests.py

@@ -1,4 +1,7 @@
 from datetime import datetime, timedelta
+import shutil
+import tempfile
+
 from django.conf import settings
 from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
 from django.contrib.sessions.backends.cache import SessionStore as CacheSession
@@ -8,9 +11,7 @@ from django.contrib.sessions.backends.base import SessionBase
 from django.contrib.sessions.models import Session
 from django.core.exceptions import ImproperlyConfigured
 from django.test import TestCase
-import shutil
-import tempfile
-import unittest
+from django.utils import unittest
 
 
 class SessionTestsMixin(object):

+ 90 - 0
django/db/backends/__init__.py

@@ -20,6 +20,7 @@ class BaseDatabaseWrapper(local):
         self.queries = []
         self.settings_dict = settings_dict
         self.alias = alias
+        self.vendor = 'unknown'
 
     def __eq__(self, other):
         return self.settings_dict == other.settings_dict
@@ -87,16 +88,105 @@ class BaseDatabaseFeatures(object):
     needs_datetime_string_cast = True
     empty_fetchmany_value = []
     update_can_self_select = True
+
+    # Does the backend distinguish between '' and None?
     interprets_empty_strings_as_nulls = False
+
     can_use_chunked_reads = True
     can_return_id_from_insert = False
     uses_autocommit = False
     uses_savepoints = False
+
     # If True, don't use integer foreign keys referring to, e.g., positive
     # integer primary keys.
     related_fields_match_type = False
     allow_sliced_subqueries = True
 
+    # Does the default test database allow multiple connections?
+    # Usually an indication that the test database is in-memory
+    test_db_allows_multiple_connections = True
+
+    # Can an object be saved without an explicit primary key?
+    supports_unspecified_pk = False
+
+    # Can a fixture contain forward references? i.e., are
+    # FK constraints checked at the end of transaction, or
+    # at the end of each save operation?
+    supports_forward_references = True
+
+    # Does a dirty transaction need to be rolled back
+    # before the cursor can be used again?
+    requires_rollback_on_dirty_transaction = False
+
+    # Does the backend allow very long model names without error?
+    supports_long_model_names = True
+
+    # Is there a REAL datatype in addition to floats/doubles?
+    has_real_datatype = False
+    supports_subqueries_in_group_by = True
+    supports_bitwise_or = True
+
+    # Do time/datetime fields have microsecond precision?
+    supports_microsecond_precision = True
+
+    # Does the __regex lookup support backreferencing and grouping?
+    supports_regex_backreferencing = True
+
+    # Can date/datetime lookups be performed using a string?
+    supports_date_lookup_using_string = True
+
+    # Can datetimes with timezones be used?
+    supports_timezones = True
+
+    # When performing a GROUP BY, is an ORDER BY NULL required
+    # to remove any ordering?
+    requires_explicit_null_ordering_when_grouping = False
+
+    # Is there a 1000 item limit on query parameters?
+    supports_1000_query_paramters = True
+
+    # Can an object have a primary key of 0? MySQL says No.
+    allows_primary_key_0 = True
+
+    # Features that need to be confirmed at runtime
+    # Cache whether the confirmation has been performed.
+    _confirmed = False
+    supports_transactions = None
+    supports_stddev = None
+
+    def __init__(self, connection):
+        self.connection = connection
+
+    def confirm(self):
+        "Perform manual checks of any database features that might vary between installs"
+        self._confirmed = True
+        self.supports_transactions = self._supports_transactions()
+        self.supports_stddev = self._supports_stddev()
+
+    def _supports_transactions(self):
+        "Confirm support for transactions"
+        cursor = self.connection.cursor()
+        cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
+        self.connection._commit()
+        cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')
+        self.connection._rollback()
+        cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')
+        count, = cursor.fetchone()
+        cursor.execute('DROP TABLE ROLLBACK_TEST')
+        self.connection._commit()
+        return count == 0
+
+    def _supports_stddev(self):
+        "Confirm support for STDDEV and related stats functions"
+        class StdDevPop(object):
+            sql_function = 'STDDEV_POP'
+
+        try:
+            self.connection.ops.check_aggregate_support(StdDevPop())
+        except DatabaseError:
+            self.supports_stddev = False
+
+
 class BaseDatabaseOperations(object):
     """
     This class encapsulates all backend-specific differences, such as the way

+ 3 - 14
django/db/backends/creation.py

@@ -347,8 +347,9 @@ class BaseDatabaseCreation(object):
 
         self.connection.close()
         self.connection.settings_dict["NAME"] = test_database_name
-        can_rollback = self._rollback_works()
-        self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
+
+        # Confirm the feature set of the test database
+        self.connection.features.confirm()
 
         # Report syncdb messages at one level lower than that requested.
         # This ensures we don't get flooded with messages during testing
@@ -405,18 +406,6 @@ class BaseDatabaseCreation(object):
 
         return test_database_name
 
-    def _rollback_works(self):
-        cursor = self.connection.cursor()
-        cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
-        self.connection._commit()
-        cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')
-        self.connection._rollback()
-        cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')
-        count, = cursor.fetchone()
-        cursor.execute('DROP TABLE ROLLBACK_TEST')
-        self.connection._commit()
-        return count == 0
-
     def destroy_test_db(self, old_database_name, verbosity=1):
         """
         Destroy a test database, prompting the user for confirmation if the

+ 1 - 1
django/db/backends/dummy/base.py

@@ -42,7 +42,7 @@ class DatabaseWrapper(object):
     _rollback = ignore
 
     def __init__(self, settings_dict, alias, *args, **kwargs):
-        self.features = BaseDatabaseFeatures()
+        self.features = BaseDatabaseFeatures(self)
         self.ops = DatabaseOperations()
         self.client = DatabaseClient(self)
         self.creation = BaseDatabaseCreation(self)

+ 10 - 2
django/db/backends/mysql/base.py

@@ -124,6 +124,14 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     allows_group_by_pk = True
     related_fields_match_type = True
     allow_sliced_subqueries = False
+    supports_forward_references = False
+    supports_long_model_names = False
+    supports_microsecond_precision = False
+    supports_regex_backreferencing = False
+    supports_date_lookup_using_string = False
+    supports_timezones = False
+    requires_explicit_null_ordering_when_grouping = True
+    allows_primary_key_0 = False
 
 class DatabaseOperations(BaseDatabaseOperations):
     compiler_module = "django.db.backends.mysql.compiler"
@@ -231,7 +239,7 @@ class DatabaseOperations(BaseDatabaseOperations):
         return 64
 
 class DatabaseWrapper(BaseDatabaseWrapper):
-
+    vendor = 'mysql'
     operators = {
         'exact': '= %s',
         'iexact': 'LIKE %s',
@@ -253,7 +261,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         super(DatabaseWrapper, self).__init__(*args, **kwargs)
 
         self.server_version = None
-        self.features = DatabaseFeatures()
+        self.features = DatabaseFeatures(self)
         self.ops = DatabaseOperations()
         self.client = DatabaseClient(self)
         self.creation = DatabaseCreation(self)

+ 5 - 3
django/db/backends/oracle/base.py

@@ -50,7 +50,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     uses_savepoints = True
     can_return_id_from_insert = True
     allow_sliced_subqueries = False
-
+    supports_subqueries_in_group_by = True
+    supports_timezones = False
+    supports_bitwise_or = False
 
 class DatabaseOperations(BaseDatabaseOperations):
     compiler_module = "django.db.backends.oracle.compiler"
@@ -314,7 +316,7 @@ WHEN (new.%(col_name)s IS NULL)
 
 
 class DatabaseWrapper(BaseDatabaseWrapper):
-
+    vendor = 'oracle'
     operators = {
         'exact': '= %s',
         'iexact': '= UPPER(%s)',
@@ -334,7 +336,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
     def __init__(self, *args, **kwargs):
         super(DatabaseWrapper, self).__init__(*args, **kwargs)
 
-        self.features = DatabaseFeatures()
+        self.features = DatabaseFeatures(self)
         self.ops = DatabaseOperations()
         self.client = DatabaseClient(self)
         self.creation = DatabaseCreation(self)

+ 4 - 1
django/db/backends/postgresql/base.py

@@ -80,8 +80,11 @@ class UnicodeCursorWrapper(object):
 
 class DatabaseFeatures(BaseDatabaseFeatures):
     uses_savepoints = True
+    requires_rollback_on_dirty_transaction = True
+    has_real_datatype = True
 
 class DatabaseWrapper(BaseDatabaseWrapper):
+    vendor = 'postgresql'
     operators = {
         'exact': '= %s',
         'iexact': '= UPPER(%s)',
@@ -108,7 +111,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
             DeprecationWarning
         )
 
-        self.features = DatabaseFeatures()
+        self.features = DatabaseFeatures(self)
         self.ops = DatabaseOperations(self)
         self.client = DatabaseClient(self)
         self.creation = DatabaseCreation(self)

+ 4 - 1
django/db/backends/postgresql_psycopg2/base.py

@@ -67,6 +67,8 @@ class CursorWrapper(object):
 class DatabaseFeatures(BaseDatabaseFeatures):
     needs_datetime_string_cast = False
     can_return_id_from_insert = False
+    requires_rollback_on_dirty_transaction = True
+    has_real_datatype = True
 
 class DatabaseOperations(PostgresqlDatabaseOperations):
     def last_executed_query(self, cursor, sql, params):
@@ -79,6 +81,7 @@ class DatabaseOperations(PostgresqlDatabaseOperations):
         return "RETURNING %s", ()
 
 class DatabaseWrapper(BaseDatabaseWrapper):
+    vendor = 'postgresql'
     operators = {
         'exact': '= %s',
         'iexact': '= UPPER(%s)',
@@ -99,7 +102,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
     def __init__(self, *args, **kwargs):
         super(DatabaseWrapper, self).__init__(*args, **kwargs)
 
-        self.features = DatabaseFeatures()
+        self.features = DatabaseFeatures(self)
         autocommit = self.settings_dict["OPTIONS"].get('autocommit', False)
         self.features.uses_autocommit = autocommit
         self._set_isolation_level(int(not autocommit))

+ 23 - 2
django/db/backends/sqlite3/base.py

@@ -60,6 +60,27 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     # setting ensures we always read result sets fully into memory all in one
     # go.
     can_use_chunked_reads = False
+    test_db_allows_multiple_connections = False
+    supports_unspecified_pk = True
+    supports_1000_query_paramters = False
+
+    def _supports_stddev(self):
+        """Confirm support for STDDEV and related stats functions
+
+        SQLite supports STDDEV as an extension package; so
+        connection.ops.check_aggregate_support() can't unilaterally
+        rule out support for STDDEV. We need to manually check
+        whether the call works.
+        """
+        cursor = self.connection.cursor()
+        cursor.execute('CREATE TABLE STDDEV_TEST (X INT)')
+        try:
+            cursor.execute('SELECT STDDEV(*) FROM STDDEV_TEST')
+            has_support = True
+        except utils.DatabaseError:
+            has_support = False
+        cursor.execute('DROP TABLE STDDEV_TEST')
+        return has_support
 
 class DatabaseOperations(BaseDatabaseOperations):
     def date_extract_sql(self, lookup_type, field_name):
@@ -129,7 +150,7 @@ class DatabaseOperations(BaseDatabaseOperations):
         return value
 
 class DatabaseWrapper(BaseDatabaseWrapper):
-
+    vendor = 'sqlite'
     # SQLite requires LIKE statements to include an ESCAPE clause if the value
     # being escaped has a percent or underscore in it.
     # See http://www.sqlite.org/lang_expr.html for an explanation.
@@ -153,7 +174,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
     def __init__(self, *args, **kwargs):
         super(DatabaseWrapper, self).__init__(*args, **kwargs)
 
-        self.features = DatabaseFeatures()
+        self.features = DatabaseFeatures(self)
         self.ops = DatabaseOperations()
         self.client = DatabaseClient(self)
         self.creation = DatabaseCreation(self)

+ 1 - 1
django/test/__init__.py

@@ -3,5 +3,5 @@ Django Unit Test and Doctest framework.
 """
 
 from django.test.client import Client
-from django.test.testcases import TestCase, TransactionTestCase
+from django.test.testcases import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
 from django.test.utils import Approximate

+ 11 - 48
django/test/simple.py

@@ -1,12 +1,12 @@
 import sys
 import signal
-import unittest
 
 from django.conf import settings
 from django.db.models import get_app, get_apps
 from django.test import _doctest as doctest
 from django.test.utils import setup_test_environment, teardown_test_environment
 from django.test.testcases import OutputChecker, DocTestRunner, TestCase
+from django.utils import unittest
 
 # The module name for tests outside models.py
 TEST_MODULE = 'tests'
@@ -14,52 +14,13 @@ TEST_MODULE = 'tests'
 doctestOutputChecker = OutputChecker()
 
 class DjangoTestRunner(unittest.TextTestRunner):
-
-    def __init__(self, verbosity=0, failfast=False, **kwargs):
-        super(DjangoTestRunner, self).__init__(verbosity=verbosity, **kwargs)
-        self.failfast = failfast
-        self._keyboard_interrupt_intercepted = False
-
-    def run(self, *args, **kwargs):
-        """
-        Runs the test suite after registering a custom signal handler
-        that triggers a graceful exit when Ctrl-C is pressed.
-        """
-        self._default_keyboard_interrupt_handler = signal.signal(signal.SIGINT,
-            self._keyboard_interrupt_handler)
-        try:
-            result = super(DjangoTestRunner, self).run(*args, **kwargs)
-        finally:
-            signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler)
-        return result
-
-    def _keyboard_interrupt_handler(self, signal_number, stack_frame):
-        """
-        Handles Ctrl-C by setting a flag that will stop the test run when
-        the currently running test completes.
-        """
-        self._keyboard_interrupt_intercepted = True
-        sys.stderr.write(" <Test run halted by Ctrl-C> ")
-        # Set the interrupt handler back to the default handler, so that
-        # another Ctrl-C press will trigger immediate exit.
-        signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler)
-
-    def _makeResult(self):
-        result = super(DjangoTestRunner, self)._makeResult()
-        failfast = self.failfast
-
-        def stoptest_override(func):
-            def stoptest(test):
-                # If we were set to failfast and the unit test failed,
-                # or if the user has typed Ctrl-C, report and quit
-                if (failfast and not result.wasSuccessful()) or \
-                    self._keyboard_interrupt_intercepted:
-                    result.stop()
-                func(test)
-            return stoptest
-
-        result.stopTest = stoptest_override(result.stopTest)
-        return result
+    def __init__(self, *args, **kwargs):
+        import warnings
+        warnings.warn(
+            "DjangoTestRunner is deprecated; it's functionality is indistinguishable from TextTestRunner",
+            PendingDeprecationWarning
+        )
+        super(DjangoTestRunner, self).__init__(*args, **kwargs)
 
 def get_tests(app_module):
     try:
@@ -232,6 +193,7 @@ class DjangoTestSuiteRunner(object):
     def setup_test_environment(self, **kwargs):
         setup_test_environment()
         settings.DEBUG = False
+        unittest.installHandler()
 
     def build_suite(self, test_labels, extra_tests=None, **kwargs):
         suite = unittest.TestSuite()
@@ -271,7 +233,7 @@ class DjangoTestSuiteRunner(object):
         return old_names, mirrors
 
     def run_suite(self, suite, **kwargs):
-        return DjangoTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
+        return unittest.TextTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
 
     def teardown_databases(self, old_config, **kwargs):
         from django.db import connections
@@ -284,6 +246,7 @@ class DjangoTestSuiteRunner(object):
             connection.creation.destroy_test_db(old_name, self.verbosity)
 
     def teardown_test_environment(self, **kwargs):
+        unittest.removeHandler()
         teardown_test_environment()
 
     def suite_result(self, suite, result, **kwargs):

+ 28 - 4
django/test/testcases.py

@@ -1,5 +1,4 @@
 import re
-import unittest
 from urlparse import urlsplit, urlunsplit
 from xml.dom.minidom import parseString, Node
 
@@ -7,12 +6,14 @@ from django.conf import settings
 from django.core import mail
 from django.core.management import call_command
 from django.core.urlresolvers import clear_url_caches
-from django.db import transaction, connections, DEFAULT_DB_ALIAS
+from django.db import transaction, connection, connections, DEFAULT_DB_ALIAS
 from django.http import QueryDict
 from django.test import _doctest as doctest
 from django.test.client import Client
-from django.utils import simplejson
+from django.utils import simplejson, unittest
 from django.utils.encoding import smart_str
+from django.utils.functional import wraps
+
 
 try:
     all
@@ -22,6 +23,7 @@ except NameError:
 normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
 normalize_decimals = lambda s: re.sub(r"Decimal\('(\d+(\.\d*)?)'\)", lambda m: "Decimal(\"%s\")" % m.groups()[0], s)
 
+
 def to_list(value):
     """
     Puts value into a list if it's not already one.
@@ -472,7 +474,7 @@ def connections_support_transactions():
     Returns True if all connections support transactions.  This is messy
     because 2.4 doesn't support any or all.
     """
-    return all(conn.settings_dict['SUPPORTS_TRANSACTIONS']
+    return all(conn.features.supports_transactions
         for conn in connections.all())
 
 class TestCase(TransactionTestCase):
@@ -528,3 +530,25 @@ class TestCase(TransactionTestCase):
 
         for connection in connections.all():
             connection.close()
+
+def _deferredSkip(condition, reason):
+    def decorator(test_item):
+        if not (isinstance(test_item, type) and issubclass(test_item, TestCase)):
+            @wraps(test_item)
+            def skip_wrapper(*args, **kwargs):
+                if condition():
+                    raise unittest.SkipTest(reason)
+            test_item = skip_wrapper
+        test_item.__unittest_skip_why__ = reason
+        return test_item
+    return decorator
+
+def skipIfDBFeature(feature):
+    "Skip a test if a database has the named feature"
+    return _deferredSkip(lambda: getattr(connection.features, feature),
+                         "Database has feature %s" % feature)
+
+def skipUnlessDBFeature(feature):
+    "Skip a test unless a database has the named feature"
+    return _deferredSkip(lambda: not getattr(connection.features, feature),
+                         "Database doesn't support feature %s" % feature)

+ 80 - 0
django/utils/unittest/__init__.py

@@ -0,0 +1,80 @@
+"""
+unittest2
+
+unittest2 is a backport of the new features added to the unittest testing
+framework in Python 2.7. It is tested to run on Python 2.4 - 2.6.
+
+To use unittest2 instead of unittest simply replace ``import unittest`` with
+``import unittest2``.
+
+
+Copyright (c) 1999-2003 Steve Purcell
+Copyright (c) 2003-2010 Python Software Foundation
+This module is free software, and you may redistribute it and/or modify
+it under the same terms as Python itself, so long as this copyright message
+and disclaimer are retained in their original form.
+
+IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
+THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
+AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
+SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+"""
+
+import sys
+
+# Django hackery to load the appropriate version of unittest
+
+if sys.version_info >= (2,7):
+    # unittest2 features are native in Python 2.7
+    from unittest import *
+else:
+    try:
+        # check the system path first
+        from unittest2 import *
+    except ImportError:
+        # otherwise use our bundled version
+        __all__ = ['TestResult', 'TestCase', 'TestSuite',
+                   'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
+                   'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
+                   'expectedFailure', 'TextTestResult', '__version__', 'collector']
+
+        __version__ = '0.5.1'
+
+        # Expose obsolete functions for backwards compatibility
+        __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
+
+
+        from django.utils.unittest.collector import collector
+        from django.utils.unittest.result import TestResult
+        from django.utils.unittest.case import \
+            TestCase, FunctionTestCase, SkipTest, skip, skipIf,\
+            skipUnless, expectedFailure
+
+        from django.utils.unittest.suite import BaseTestSuite, TestSuite
+        from django.utils.unittest.loader import \
+            TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,\
+            findTestCases
+
+        from django.utils.unittest.main import TestProgram, main, main_
+        from django.utils.unittest.runner import TextTestRunner, TextTestResult
+
+        try:
+            from django.utils.unittest.signals import\
+                installHandler, registerResult, removeResult, removeHandler
+        except ImportError:
+            # Compatibility with platforms that don't have the signal module
+            pass
+        else:
+            __all__.extend(['installHandler', 'registerResult', 'removeResult',
+                            'removeHandler'])
+
+        # deprecated
+        _TextTestResult = TextTestResult
+
+        __unittest = True

+ 10 - 0
django/utils/unittest/__main__.py

@@ -0,0 +1,10 @@
+"""Main entry point"""
+
+import sys
+if sys.argv[0].endswith("__main__.py"):
+    sys.argv[0] = "unittest2"
+
+__unittest = True
+
+from django.utils.unittest.main import main_
+main_()

+ 1083 - 0
django/utils/unittest/case.py

@@ -0,0 +1,1083 @@
+"""Test case implementation"""
+
+import sys
+import difflib
+import pprint
+import re
+import unittest
+import warnings
+
+from django.utils.unittest import result
+from django.utils.unittest.util import\
+    safe_repr, safe_str, strclass,\
+    unorderable_list_difference
+
+from django.utils.unittest.compatibility import wraps
+
+__unittest = True
+
+
+DIFF_OMITTED = ('\nDiff is %s characters long. '
+                 'Set self.maxDiff to None to see it.')
+
+class SkipTest(Exception):
+    """
+    Raise this exception in a test to skip it.
+
+    Usually you can use TestResult.skip() or one of the skipping decorators
+    instead of raising this directly.
+    """
+
+class _ExpectedFailure(Exception):
+    """
+    Raise this when a test is expected to fail.
+
+    This is an implementation detail.
+    """
+
+    def __init__(self, exc_info):
+        # can't use super because Python 2.4 exceptions are old style
+        Exception.__init__(self)
+        self.exc_info = exc_info
+
+class _UnexpectedSuccess(Exception):
+    """
+    The test was supposed to fail, but it didn't!
+    """
+
+def _id(obj):
+    return obj
+
+def skip(reason):
+    """
+    Unconditionally skip a test.
+    """
+    def decorator(test_item):
+        if not (isinstance(test_item, type) and issubclass(test_item, TestCase)):
+            @wraps(test_item)
+            def skip_wrapper(*args, **kwargs):
+                raise SkipTest(reason)
+            test_item = skip_wrapper
+
+        test_item.__unittest_skip__ = True
+        test_item.__unittest_skip_why__ = reason
+        return test_item
+    return decorator
+
+def skipIf(condition, reason):
+    """
+    Skip a test if the condition is true.
+    """
+    if condition:
+        return skip(reason)
+    return _id
+
+def skipUnless(condition, reason):
+    """
+    Skip a test unless the condition is true.
+    """
+    if not condition:
+        return skip(reason)
+    return _id
+
+
+def expectedFailure(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        try:
+            func(*args, **kwargs)
+        except Exception:
+            raise _ExpectedFailure(sys.exc_info())
+        raise _UnexpectedSuccess
+    return wrapper
+
+
+class _AssertRaisesContext(object):
+    """A context manager used to implement TestCase.assertRaises* methods."""
+
+    def __init__(self, expected, test_case, expected_regexp=None):
+        self.expected = expected
+        self.failureException = test_case.failureException
+        self.expected_regexp = expected_regexp
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, tb):
+        if exc_type is None:
+            try:
+                exc_name = self.expected.__name__
+            except AttributeError:
+                exc_name = str(self.expected)
+            raise self.failureException(
+                "%s not raised" % (exc_name,))
+        if not issubclass(exc_type, self.expected):
+            # let unexpected exceptions pass through
+            return False
+        self.exception = exc_value # store for later retrieval
+        if self.expected_regexp is None:
+            return True
+
+        expected_regexp = self.expected_regexp
+        if isinstance(expected_regexp, basestring):
+            expected_regexp = re.compile(expected_regexp)
+        if not expected_regexp.search(str(exc_value)):
+            raise self.failureException('"%s" does not match "%s"' %
+                     (expected_regexp.pattern, str(exc_value)))
+        return True
+
+
+class _TypeEqualityDict(object):
+
+    def __init__(self, testcase):
+        self.testcase = testcase
+        self._store = {}
+
+    def __setitem__(self, key, value):
+        self._store[key] = value
+
+    def __getitem__(self, key):
+        value = self._store[key]
+        if isinstance(value, basestring):
+            return getattr(self.testcase, value)
+        return value
+
+    def get(self, key, default=None):
+        if key in self._store:
+            return self[key]
+        return default
+
+
+class TestCase(unittest.TestCase):
+    """A class whose instances are single test cases.
+
+    By default, the test code itself should be placed in a method named
+    'runTest'.
+
+    If the fixture may be used for many test cases, create as
+    many test methods as are needed. When instantiating such a TestCase
+    subclass, specify in the constructor arguments the name of the test method
+    that the instance is to execute.
+
+    Test authors should subclass TestCase for their own tests. Construction
+    and deconstruction of the test's environment ('fixture') can be
+    implemented by overriding the 'setUp' and 'tearDown' methods respectively.
+
+    If it is necessary to override the __init__ method, the base class
+    __init__ method must always be called. It is important that subclasses
+    should not change the signature of their __init__ method, since instances
+    of the classes are instantiated automatically by parts of the framework
+    in order to be run.
+    """
+
+    # This attribute determines which exception will be raised when
+    # the instance's assertion methods fail; test methods raising this
+    # exception will be deemed to have 'failed' rather than 'errored'
+
+    failureException = AssertionError
+
+    # This attribute sets the maximum length of a diff in failure messages
+    # by assert methods using difflib. It is looked up as an instance attribute
+    # so can be configured by individual tests if required.
+
+    maxDiff = 80*8
+
+    # This attribute determines whether long messages (including repr of
+    # objects used in assert methods) will be printed on failure in *addition*
+    # to any explicit message passed.
+
+    longMessage = True
+
+    # Attribute used by TestSuite for classSetUp
+
+    _classSetupFailed = False
+
+    def __init__(self, methodName='runTest'):
+        """Create an instance of the class that will use the named test
+           method when executed. Raises a ValueError if the instance does
+           not have a method with the specified name.
+        """
+        self._testMethodName = methodName
+        self._resultForDoCleanups = None
+        try:
+            testMethod = getattr(self, methodName)
+        except AttributeError:
+            raise ValueError("no such test method in %s: %s" % \
+                  (self.__class__, methodName))
+        self._testMethodDoc = testMethod.__doc__
+        self._cleanups = []
+
+        # Map types to custom assertEqual functions that will compare
+        # instances of said type in more detail to generate a more useful
+        # error message.
+        self._type_equality_funcs = _TypeEqualityDict(self)
+        self.addTypeEqualityFunc(dict, 'assertDictEqual')
+        self.addTypeEqualityFunc(list, 'assertListEqual')
+        self.addTypeEqualityFunc(tuple, 'assertTupleEqual')
+        self.addTypeEqualityFunc(set, 'assertSetEqual')
+        self.addTypeEqualityFunc(frozenset, 'assertSetEqual')
+        self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual')
+
+    def addTypeEqualityFunc(self, typeobj, function):
+        """Add a type specific assertEqual style function to compare a type.
+
+        This method is for use by TestCase subclasses that need to register
+        their own type equality functions to provide nicer error messages.
+
+        Args:
+            typeobj: The data type to call this function on when both values
+                    are of the same type in assertEqual().
+            function: The callable taking two arguments and an optional
+                    msg= argument that raises self.failureException with a
+                    useful error message when the two arguments are not equal.
+        """
+        self._type_equality_funcs[typeobj] = function
+
+    def addCleanup(self, function, *args, **kwargs):
+        """Add a function, with arguments, to be called when the test is
+        completed. Functions added are called on a LIFO basis and are
+        called after tearDown on test failure or success.
+
+        Cleanup items are called even if setUp fails (unlike tearDown)."""
+        self._cleanups.append((function, args, kwargs))
+
+    def setUp(self):
+        "Hook method for setting up the test fixture before exercising it."
+
+    @classmethod
+    def setUpClass(cls):
+        "Hook method for setting up class fixture before running tests in the class."
+
+    @classmethod
+    def tearDownClass(cls):
+        "Hook method for deconstructing the class fixture after running all tests in the class."
+
+    def tearDown(self):
+        "Hook method for deconstructing the test fixture after testing it."
+
+    def countTestCases(self):
+        return 1
+
+    def defaultTestResult(self):
+        return result.TestResult()
+
+    def shortDescription(self):
+        """Returns a one-line description of the test, or None if no
+        description has been provided.
+
+        The default implementation of this method returns the first line of
+        the specified test method's docstring.
+        """
+        doc = self._testMethodDoc
+        return doc and doc.split("\n")[0].strip() or None
+
+
+    def id(self):
+        return "%s.%s" % (strclass(self.__class__), self._testMethodName)
+
+    def __eq__(self, other):
+        if type(self) is not type(other):
+            return NotImplemented
+
+        return self._testMethodName == other._testMethodName
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __hash__(self):
+        return hash((type(self), self._testMethodName))
+
+    def __str__(self):
+        return "%s (%s)" % (self._testMethodName, strclass(self.__class__))
+
+    def __repr__(self):
+        return "<%s testMethod=%s>" % \
+               (strclass(self.__class__), self._testMethodName)
+
+    def _addSkip(self, result, reason):
+        addSkip = getattr(result, 'addSkip', None)
+        if addSkip is not None:
+            addSkip(self, reason)
+        else:
+            warnings.warn("Use of a TestResult without an addSkip method is deprecated",
+                          DeprecationWarning, 2)
+            result.addSuccess(self)
+
+    def run(self, result=None):
+        orig_result = result
+        if result is None:
+            result = self.defaultTestResult()
+            startTestRun = getattr(result, 'startTestRun', None)
+            if startTestRun is not None:
+                startTestRun()
+
+        self._resultForDoCleanups = result
+        result.startTest(self)
+
+        testMethod = getattr(self, self._testMethodName)
+
+        if (getattr(self.__class__, "__unittest_skip__", False) or
+            getattr(testMethod, "__unittest_skip__", False)):
+            # If the class or method was skipped.
+            try:
+                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
+                            or getattr(testMethod, '__unittest_skip_why__', ''))
+                self._addSkip(result, skip_why)
+            finally:
+                result.stopTest(self)
+            return
+        try:
+            success = False
+            try:
+                self.setUp()
+            except SkipTest, e:
+                self._addSkip(result, str(e))
+            except Exception:
+                result.addError(self, sys.exc_info())
+            else:
+                try:
+                    testMethod()
+                except self.failureException:
+                    result.addFailure(self, sys.exc_info())
+                except _ExpectedFailure, e:
+                    addExpectedFailure = getattr(result, 'addExpectedFailure', None)
+                    if addExpectedFailure is not None:
+                        addExpectedFailure(self, e.exc_info)
+                    else:
+                        warnings.warn("Use of a TestResult without an addExpectedFailure method is deprecated",
+                                      DeprecationWarning)
+                        result.addSuccess(self)
+                except _UnexpectedSuccess:
+                    addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
+                    if addUnexpectedSuccess is not None:
+                        addUnexpectedSuccess(self)
+                    else:
+                        warnings.warn("Use of a TestResult without an addUnexpectedSuccess method is deprecated",
+                                      DeprecationWarning)
+                        result.addFailure(self, sys.exc_info())
+                except SkipTest, e:
+                    self._addSkip(result, str(e))
+                except Exception:
+                    result.addError(self, sys.exc_info())
+                else:
+                    success = True
+
+                try:
+                    self.tearDown()
+                except Exception:
+                    result.addError(self, sys.exc_info())
+                    success = False
+
+            cleanUpSuccess = self.doCleanups()
+            success = success and cleanUpSuccess
+            if success:
+                result.addSuccess(self)
+        finally:
+            result.stopTest(self)
+            if orig_result is None:
+                stopTestRun = getattr(result, 'stopTestRun', None)
+                if stopTestRun is not None:
+                    stopTestRun()
+
+    def doCleanups(self):
+        """Execute all cleanup functions. Normally called for you after
+        tearDown."""
+        result = self._resultForDoCleanups
+        ok = True
+        while self._cleanups:
+            function, args, kwargs = self._cleanups.pop(-1)
+            try:
+                function(*args, **kwargs)
+            except Exception:
+                ok = False
+                result.addError(self, sys.exc_info())
+        return ok
+
+    def __call__(self, *args, **kwds):
+        return self.run(*args, **kwds)
+
+    def debug(self):
+        """Run the test without collecting errors in a TestResult"""
+        self.setUp()
+        getattr(self, self._testMethodName)()
+        self.tearDown()
+        while self._cleanups:
+            function, args, kwargs = self._cleanups.pop(-1)
+            function(*args, **kwargs)
+
+    def skipTest(self, reason):
+        """Skip this test."""
+        raise SkipTest(reason)
+
+    def fail(self, msg=None):
+        """Fail immediately, with the given message."""
+        raise self.failureException(msg)
+
+    def assertFalse(self, expr, msg=None):
+        "Fail the test if the expression is true."
+        if expr:
+            msg = self._formatMessage(msg, "%s is not False" % safe_repr(expr))
+            raise self.failureException(msg)
+
+    def assertTrue(self, expr, msg=None):
+        """Fail the test unless the expression is true."""
+        if not expr:
+            msg = self._formatMessage(msg, "%s is not True" % safe_repr(expr))
+            raise self.failureException(msg)
+
+    def _formatMessage(self, msg, standardMsg):
+        """Honour the longMessage attribute when generating failure messages.
+        If longMessage is False this means:
+        * Use only an explicit message if it is provided
+        * Otherwise use the standard message for the assert
+
+        If longMessage is True:
+        * Use the standard message
+        * If an explicit message is provided, plus ' : ' and the explicit message
+        """
+        if not self.longMessage:
+            return msg or standardMsg
+        if msg is None:
+            return standardMsg
+        try:
+            return '%s : %s' % (standardMsg, msg)
+        except UnicodeDecodeError:
+            return '%s : %s' % (safe_str(standardMsg), safe_str(msg))
+
+
+    def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
+        """Fail unless an exception of class excClass is thrown
+           by callableObj when invoked with arguments args and keyword
+           arguments kwargs. If a different type of exception is
+           thrown, it will not be caught, and the test case will be
+           deemed to have suffered an error, exactly as for an
+           unexpected exception.
+
+           If called with callableObj omitted or None, will return a
+           context object used like this::
+
+                with self.assertRaises(SomeException):
+                    do_something()
+
+           The context manager keeps a reference to the exception as
+           the 'exception' attribute. This allows you to inspect the
+           exception after the assertion::
+
+               with self.assertRaises(SomeException) as cm:
+                   do_something()
+               the_exception = cm.exception
+               self.assertEqual(the_exception.error_code, 3)
+        """
+        if callableObj is None:
+            return _AssertRaisesContext(excClass, self)
+        try:
+            callableObj(*args, **kwargs)
+        except excClass:
+            return
+
+        if hasattr(excClass,'__name__'):
+            excName = excClass.__name__
+        else:
+            excName = str(excClass)
+        raise self.failureException, "%s not raised" % excName
+
+    def _getAssertEqualityFunc(self, first, second):
+        """Get a detailed comparison function for the types of the two args.
+
+        Returns: A callable accepting (first, second, msg=None) that will
+        raise a failure exception if first != second with a useful human
+        readable error message for those types.
+        """
+        #
+        # NOTE(gregory.p.smith): I considered isinstance(first, type(second))
+        # and vice versa.  I opted for the conservative approach in case
+        # subclasses are not intended to be compared in detail to their super
+        # class instances using a type equality func.  This means testing
+        # subtypes won't automagically use the detailed comparison.  Callers
+        # should use their type specific assertSpamEqual method to compare
+        # subclasses if the detailed comparison is desired and appropriate.
+        # See the discussion in http://bugs.python.org/issue2578.
+        #
+        if type(first) is type(second):
+            asserter = self._type_equality_funcs.get(type(first))
+            if asserter is not None:
+                return asserter
+
+        return self._baseAssertEqual
+
+    def _baseAssertEqual(self, first, second, msg=None):
+        """The default assertEqual implementation, not type specific."""
+        if not first == second:
+            standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second))
+            msg = self._formatMessage(msg, standardMsg)
+            raise self.failureException(msg)
+
+    def assertEqual(self, first, second, msg=None):
+        """Fail if the two objects are unequal as determined by the '=='
+           operator.
+        """
+        assertion_func = self._getAssertEqualityFunc(first, second)
+        assertion_func(first, second, msg=msg)
+
+    def assertNotEqual(self, first, second, msg=None):
+        """Fail if the two objects are equal as determined by the '=='
+           operator.
+        """
+        if not first != second:
+            msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first),
+                                                           safe_repr(second)))
+            raise self.failureException(msg)
+
+    def assertAlmostEqual(self, first, second, places=None, msg=None, delta=None):
+        """Fail if the two objects are unequal as determined by their
+           difference rounded to the given number of decimal places
+           (default 7) and comparing to zero, or by comparing that the
+           between the two objects is more than the given delta.
+
+           Note that decimal places (from zero) are usually not the same
+           as significant digits (measured from the most signficant digit).
+
+           If the two objects compare equal then they will automatically
+           compare almost equal.
+        """
+        if first == second:
+            # shortcut
+            return
+        if delta is not None and places is not None:
+            raise TypeError("specify delta or places not both")
+
+        if delta is not None:
+            if abs(first - second) <= delta:
+                return
+
+            standardMsg = '%s != %s within %s delta' % (safe_repr(first),
+                                                        safe_repr(second),
+                                                        safe_repr(delta))
+        else:
+            if places is None:
+                places = 7
+
+            if round(abs(second-first), places) == 0:
+                return
+
+            standardMsg = '%s != %s within %r places' % (safe_repr(first),
+                                                          safe_repr(second),
+                                                          places)
+        msg = self._formatMessage(msg, standardMsg)
+        raise self.failureException(msg)
+
+    def assertNotAlmostEqual(self, first, second, places=None, msg=None, delta=None):
+        """Fail if the two objects are equal as determined by their
+           difference rounded to the given number of decimal places
+           (default 7) and comparing to zero, or by comparing that the
+           between the two objects is less than the given delta.
+
+           Note that decimal places (from zero) are usually not the same
+           as significant digits (measured from the most signficant digit).
+
+           Objects that are equal automatically fail.
+        """
+        if delta is not None and places is not None:
+            raise TypeError("specify delta or places not both")
+        if delta is not None:
+            if not (first == second) and abs(first - second) > delta:
+                return
+            standardMsg = '%s == %s within %s delta' % (safe_repr(first),
+                                                        safe_repr(second),
+                                                        safe_repr(delta))
+        else:
+            if places is None:
+                places = 7
+            if not (first == second) and round(abs(second-first), places) != 0:
+                return
+            standardMsg = '%s == %s within %r places' % (safe_repr(first),
+                                                         safe_repr(second),
+                                                         places)
+
+        msg = self._formatMessage(msg, standardMsg)
+        raise self.failureException(msg)
+
+    # Synonyms for assertion methods
+
+    # The plurals are undocumented.  Keep them that way to discourage use.
+    # Do not add more.  Do not remove.
+    # Going through a deprecation cycle on these would annoy many people.
+    assertEquals = assertEqual
+    assertNotEquals = assertNotEqual
+    assertAlmostEquals = assertAlmostEqual
+    assertNotAlmostEquals = assertNotAlmostEqual
+    assert_ = assertTrue
+
+    # These fail* assertion method names are pending deprecation and will
+    # be a DeprecationWarning in 3.2; http://bugs.python.org/issue2578
+    def _deprecate(original_func):
+        def deprecated_func(*args, **kwargs):
+            warnings.warn(
+                ('Please use %s instead.' % original_func.__name__),
+                PendingDeprecationWarning, 2)
+            return original_func(*args, **kwargs)
+        return deprecated_func
+
+    failUnlessEqual = _deprecate(assertEqual)
+    failIfEqual = _deprecate(assertNotEqual)
+    failUnlessAlmostEqual = _deprecate(assertAlmostEqual)
+    failIfAlmostEqual = _deprecate(assertNotAlmostEqual)
+    failUnless = _deprecate(assertTrue)
+    failUnlessRaises = _deprecate(assertRaises)
+    failIf = _deprecate(assertFalse)
+
+    def assertSequenceEqual(self, seq1, seq2,
+                            msg=None, seq_type=None, max_diff=80*8):
+        """An equality assertion for ordered sequences (like lists and tuples).
+
+        For the purposes of this function, a valid ordered sequence type is one
+        which can be indexed, has a length, and has an equality operator.
+
+        Args:
+            seq1: The first sequence to compare.
+            seq2: The second sequence to compare.
+            seq_type: The expected datatype of the sequences, or None if no
+                    datatype should be enforced.
+            msg: Optional message to use on failure instead of a list of
+                    differences.
+            max_diff: Maximum size off the diff, larger diffs are not shown
+        """
+        if seq_type is not None:
+            seq_type_name = seq_type.__name__
+            if not isinstance(seq1, seq_type):
+                raise self.failureException('First sequence is not a %s: %s'
+                                            % (seq_type_name, safe_repr(seq1)))
+            if not isinstance(seq2, seq_type):
+                raise self.failureException('Second sequence is not a %s: %s'
+                                            % (seq_type_name, safe_repr(seq2)))
+        else:
+            seq_type_name = "sequence"
+
+        differing = None
+        try:
+            len1 = len(seq1)
+        except (TypeError, NotImplementedError):
+            differing = 'First %s has no length.    Non-sequence?' % (
+                    seq_type_name)
+
+        if differing is None:
+            try:
+                len2 = len(seq2)
+            except (TypeError, NotImplementedError):
+                differing = 'Second %s has no length.    Non-sequence?' % (
+                        seq_type_name)
+
+        if differing is None:
+            if seq1 == seq2:
+                return
+
+            seq1_repr = repr(seq1)
+            seq2_repr = repr(seq2)
+            if len(seq1_repr) > 30:
+                seq1_repr = seq1_repr[:30] + '...'
+            if len(seq2_repr) > 30:
+                seq2_repr = seq2_repr[:30] + '...'
+            elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr)
+            differing = '%ss differ: %s != %s\n' % elements
+
+            for i in xrange(min(len1, len2)):
+                try:
+                    item1 = seq1[i]
+                except (TypeError, IndexError, NotImplementedError):
+                    differing += ('\nUnable to index element %d of first %s\n' %
+                                 (i, seq_type_name))
+                    break
+
+                try:
+                    item2 = seq2[i]
+                except (TypeError, IndexError, NotImplementedError):
+                    differing += ('\nUnable to index element %d of second %s\n' %
+                                 (i, seq_type_name))
+                    break
+
+                if item1 != item2:
+                    differing += ('\nFirst differing element %d:\n%s\n%s\n' %
+                                 (i, item1, item2))
+                    break
+            else:
+                if (len1 == len2 and seq_type is None and
+                    type(seq1) != type(seq2)):
+                    # The sequences are the same, but have differing types.
+                    return
+
+            if len1 > len2:
+                differing += ('\nFirst %s contains %d additional '
+                             'elements.\n' % (seq_type_name, len1 - len2))
+                try:
+                    differing += ('First extra element %d:\n%s\n' %
+                                  (len2, seq1[len2]))
+                except (TypeError, IndexError, NotImplementedError):
+                    differing += ('Unable to index element %d '
+                                  'of first %s\n' % (len2, seq_type_name))
+            elif len1 < len2:
+                differing += ('\nSecond %s contains %d additional '
+                             'elements.\n' % (seq_type_name, len2 - len1))
+                try:
+                    differing += ('First extra element %d:\n%s\n' %
+                                  (len1, seq2[len1]))
+                except (TypeError, IndexError, NotImplementedError):
+                    differing += ('Unable to index element %d '
+                                  'of second %s\n' % (len1, seq_type_name))
+        standardMsg = differing
+        diffMsg = '\n' + '\n'.join(
+            difflib.ndiff(pprint.pformat(seq1).splitlines(),
+                          pprint.pformat(seq2).splitlines()))
+
+        standardMsg = self._truncateMessage(standardMsg, diffMsg)
+        msg = self._formatMessage(msg, standardMsg)
+        self.fail(msg)
+
+    def _truncateMessage(self, message, diff):
+        max_diff = self.maxDiff
+        if max_diff is None or len(diff) <= max_diff:
+            return message + diff
+        return message + (DIFF_OMITTED % len(diff))
+
+    def assertListEqual(self, list1, list2, msg=None):
+        """A list-specific equality assertion.
+
+        Args:
+            list1: The first list to compare.
+            list2: The second list to compare.
+            msg: Optional message to use on failure instead of a list of
+                    differences.
+
+        """
+        self.assertSequenceEqual(list1, list2, msg, seq_type=list)
+
+    def assertTupleEqual(self, tuple1, tuple2, msg=None):
+        """A tuple-specific equality assertion.
+
+        Args:
+            tuple1: The first tuple to compare.
+            tuple2: The second tuple to compare.
+            msg: Optional message to use on failure instead of a list of
+                    differences.
+        """
+        self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple)
+
+    def assertSetEqual(self, set1, set2, msg=None):
+        """A set-specific equality assertion.
+
+        Args:
+            set1: The first set to compare.
+            set2: The second set to compare.
+            msg: Optional message to use on failure instead of a list of
+                    differences.
+
+        assertSetEqual uses ducktyping to support
+        different types of sets, and is optimized for sets specifically
+        (parameters must support a difference method).
+        """
+        try:
+            difference1 = set1.difference(set2)
+        except TypeError, e:
+            self.fail('invalid type when attempting set difference: %s' % e)
+        except AttributeError, e:
+            self.fail('first argument does not support set difference: %s' % e)
+
+        try:
+            difference2 = set2.difference(set1)
+        except TypeError, e:
+            self.fail('invalid type when attempting set difference: %s' % e)
+        except AttributeError, e:
+            self.fail('second argument does not support set difference: %s' % e)
+
+        if not (difference1 or difference2):
+            return
+
+        lines = []
+        if difference1:
+            lines.append('Items in the first set but not the second:')
+            for item in difference1:
+                lines.append(repr(item))
+        if difference2:
+            lines.append('Items in the second set but not the first:')
+            for item in difference2:
+                lines.append(repr(item))
+
+        standardMsg = '\n'.join(lines)
+        self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertIn(self, member, container, msg=None):
+        """Just like self.assertTrue(a in b), but with a nicer default message."""
+        if member not in container:
+            standardMsg = '%s not found in %s' % (safe_repr(member),
+                                                   safe_repr(container))
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertNotIn(self, member, container, msg=None):
+        """Just like self.assertTrue(a not in b), but with a nicer default message."""
+        if member in container:
+            standardMsg = '%s unexpectedly found in %s' % (safe_repr(member),
+                                                            safe_repr(container))
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertIs(self, expr1, expr2, msg=None):
+        """Just like self.assertTrue(a is b), but with a nicer default message."""
+        if expr1 is not expr2:
+            standardMsg = '%s is not %s' % (safe_repr(expr1), safe_repr(expr2))
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertIsNot(self, expr1, expr2, msg=None):
+        """Just like self.assertTrue(a is not b), but with a nicer default message."""
+        if expr1 is expr2:
+            standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),)
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertDictEqual(self, d1, d2, msg=None):
+        self.assert_(isinstance(d1, dict), 'First argument is not a dictionary')
+        self.assert_(isinstance(d2, dict), 'Second argument is not a dictionary')
+
+        if d1 != d2:
+            standardMsg = '%s != %s' % (safe_repr(d1, True), safe_repr(d2, True))
+            diff = ('\n' + '\n'.join(difflib.ndiff(
+                           pprint.pformat(d1).splitlines(),
+                           pprint.pformat(d2).splitlines())))
+            standardMsg = self._truncateMessage(standardMsg, diff)
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertDictContainsSubset(self, expected, actual, msg=None):
+        """Checks whether actual is a superset of expected."""
+        missing = []
+        mismatched = []
+        for key, value in expected.iteritems():
+            if key not in actual:
+                missing.append(key)
+            elif value != actual[key]:
+                mismatched.append('%s, expected: %s, actual: %s' %
+                                  (safe_repr(key), safe_repr(value),
+                                   safe_repr(actual[key])))
+
+        if not (missing or mismatched):
+            return
+
+        standardMsg = ''
+        if missing:
+            standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in
+                                                    missing)
+        if mismatched:
+            if standardMsg:
+                standardMsg += '; '
+            standardMsg += 'Mismatched values: %s' % ','.join(mismatched)
+
+        self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
+        """An unordered sequence specific comparison. It asserts that
+        expected_seq and actual_seq contain the same elements. It is
+        the equivalent of::
+
+            self.assertEqual(sorted(expected_seq), sorted(actual_seq))
+
+        Raises with an error message listing which elements of expected_seq
+        are missing from actual_seq and vice versa if any.
+
+        Asserts that each element has the same count in both sequences.
+        Example:
+            - [0, 1, 1] and [1, 0, 1] compare equal.
+            - [0, 0, 1] and [0, 1] compare unequal.
+        """
+        try:
+            expected = sorted(expected_seq)
+            actual = sorted(actual_seq)
+        except TypeError:
+            # Unsortable items (example: set(), complex(), ...)
+            expected = list(expected_seq)
+            actual = list(actual_seq)
+            missing, unexpected = unorderable_list_difference(
+                expected, actual, ignore_duplicate=False
+            )
+        else:
+            return self.assertSequenceEqual(expected, actual, msg=msg)
+
+        errors = []
+        if missing:
+            errors.append('Expected, but missing:\n    %s' %
+                           safe_repr(missing))
+        if unexpected:
+            errors.append('Unexpected, but present:\n    %s' %
+                           safe_repr(unexpected))
+        if errors:
+            standardMsg = '\n'.join(errors)
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertMultiLineEqual(self, first, second, msg=None):
+        """Assert that two multi-line strings are equal."""
+        self.assert_(isinstance(first, basestring), (
+                'First argument is not a string'))
+        self.assert_(isinstance(second, basestring), (
+                'Second argument is not a string'))
+
+        if first != second:
+            standardMsg = '%s != %s' % (safe_repr(first, True), safe_repr(second, True))
+            diff = '\n' + ''.join(difflib.ndiff(first.splitlines(True),
+                                                       second.splitlines(True)))
+            standardMsg = self._truncateMessage(standardMsg, diff)
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertLess(self, a, b, msg=None):
+        """Just like self.assertTrue(a < b), but with a nicer default message."""
+        if not a < b:
+            standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b))
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertLessEqual(self, a, b, msg=None):
+        """Just like self.assertTrue(a <= b), but with a nicer default message."""
+        if not a <= b:
+            standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b))
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertGreater(self, a, b, msg=None):
+        """Just like self.assertTrue(a > b), but with a nicer default message."""
+        if not a > b:
+            standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b))
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertGreaterEqual(self, a, b, msg=None):
+        """Just like self.assertTrue(a >= b), but with a nicer default message."""
+        if not a >= b:
+            standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b))
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertIsNone(self, obj, msg=None):
+        """Same as self.assertTrue(obj is None), with a nicer default message."""
+        if obj is not None:
+            standardMsg = '%s is not None' % (safe_repr(obj),)
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertIsNotNone(self, obj, msg=None):
+        """Included for symmetry with assertIsNone."""
+        if obj is None:
+            standardMsg = 'unexpectedly None'
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertIsInstance(self, obj, cls, msg=None):
+        """Same as self.assertTrue(isinstance(obj, cls)), with a nicer
+        default message."""
+        if not isinstance(obj, cls):
+            standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls)
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertNotIsInstance(self, obj, cls, msg=None):
+        """Included for symmetry with assertIsInstance."""
+        if isinstance(obj, cls):
+            standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls)
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertRaisesRegexp(self, expected_exception, expected_regexp,
+                           callable_obj=None, *args, **kwargs):
+        """Asserts that the message in a raised exception matches a regexp.
+
+        Args:
+            expected_exception: Exception class expected to be raised.
+            expected_regexp: Regexp (re pattern object or string) expected
+                    to be found in error message.
+            callable_obj: Function to be called.
+            args: Extra args.
+            kwargs: Extra kwargs.
+        """
+        if callable_obj is None:
+            return _AssertRaisesContext(expected_exception, self, expected_regexp)
+        try:
+            callable_obj(*args, **kwargs)
+        except expected_exception, exc_value:
+            if isinstance(expected_regexp, basestring):
+                expected_regexp = re.compile(expected_regexp)
+            if not expected_regexp.search(str(exc_value)):
+                raise self.failureException('"%s" does not match "%s"' %
+                         (expected_regexp.pattern, str(exc_value)))
+        else:
+            if hasattr(expected_exception, '__name__'):
+                excName = expected_exception.__name__
+            else:
+                excName = str(expected_exception)
+            raise self.failureException, "%s not raised" % excName
+
+
+    def assertRegexpMatches(self, text, expected_regexp, msg=None):
+        """Fail the test unless the text matches the regular expression."""
+        if isinstance(expected_regexp, basestring):
+            expected_regexp = re.compile(expected_regexp)
+        if not expected_regexp.search(text):
+            msg = msg or "Regexp didn't match"
+            msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text)
+            raise self.failureException(msg)
+
+    def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None):
+        """Fail the test if the text matches the regular expression."""
+        if isinstance(unexpected_regexp, basestring):
+            unexpected_regexp = re.compile(unexpected_regexp)
+        match = unexpected_regexp.search(text)
+        if match:
+            msg = msg or "Regexp matched"
+            msg = '%s: %r matches %r in %r' % (msg,
+                                               text[match.start():match.end()],
+                                               unexpected_regexp.pattern,
+                                               text)
+            raise self.failureException(msg)
+
+class FunctionTestCase(TestCase):
+    """A test case that wraps a test function.
+
+    This is useful for slipping pre-existing test functions into the
+    unittest framework. Optionally, set-up and tidy-up functions can be
+    supplied. As with TestCase, the tidy-up ('tearDown') function will
+    always be called if the set-up ('setUp') function ran successfully.
+    """
+
+    def __init__(self, testFunc, setUp=None, tearDown=None, description=None):
+        super(FunctionTestCase, self).__init__()
+        self._setUpFunc = setUp
+        self._tearDownFunc = tearDown
+        self._testFunc = testFunc
+        self._description = description
+
+    def setUp(self):
+        if self._setUpFunc is not None:
+            self._setUpFunc()
+
+    def tearDown(self):
+        if self._tearDownFunc is not None:
+            self._tearDownFunc()
+
+    def runTest(self):
+        self._testFunc()
+
+    def id(self):
+        return self._testFunc.__name__
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+
+        return self._setUpFunc == other._setUpFunc and \
+               self._tearDownFunc == other._tearDownFunc and \
+               self._testFunc == other._testFunc and \
+               self._description == other._description
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __hash__(self):
+        return hash((type(self), self._setUpFunc, self._tearDownFunc,
+                     self._testFunc, self._description))
+
+    def __str__(self):
+        return "%s (%s)" % (strclass(self.__class__),
+                            self._testFunc.__name__)
+
+    def __repr__(self):
+        return "<%s testFunc=%s>" % (strclass(self.__class__),
+                                     self._testFunc)
+
+    def shortDescription(self):
+        if self._description is not None:
+            return self._description
+        doc = self._testFunc.__doc__
+        return doc and doc.split("\n")[0].strip() or None

+ 9 - 0
django/utils/unittest/collector.py

@@ -0,0 +1,9 @@
+import os
+import sys
+from django.utils.unittest.loader import defaultTestLoader
+
+def collector():
+    # import __main__ triggers code re-execution
+    __main__ = sys.modules['__main__']
+    setupDir = os.path.abspath(os.path.dirname(__main__.__file__))
+    return defaultTestLoader.discover(setupDir)

+ 64 - 0
django/utils/unittest/compatibility.py

@@ -0,0 +1,64 @@
+import os
+import sys
+
+try:
+    from functools import wraps
+except ImportError:
+    # only needed for Python 2.4
+    def wraps(_):
+        def _wraps(func):
+            return func
+        return _wraps
+
+__unittest = True
+
+def _relpath_nt(path, start=os.path.curdir):
+    """Return a relative version of a path"""
+
+    if not path:
+        raise ValueError("no path specified")
+    start_list = os.path.abspath(start).split(os.path.sep)
+    path_list = os.path.abspath(path).split(os.path.sep)
+    if start_list[0].lower() != path_list[0].lower():
+        unc_path, rest = os.path.splitunc(path)
+        unc_start, rest = os.path.splitunc(start)
+        if bool(unc_path) ^ bool(unc_start):
+            raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
+                                                                % (path, start))
+        else:
+            raise ValueError("path is on drive %s, start on drive %s"
+                                                % (path_list[0], start_list[0]))
+    # Work out how much of the filepath is shared by start and path.
+    for i in range(min(len(start_list), len(path_list))):
+        if start_list[i].lower() != path_list[i].lower():
+            break
+    else:
+        i += 1
+
+    rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
+    if not rel_list:
+        return os.path.curdir
+    return os.path.join(*rel_list)
+
+# default to posixpath definition
+def _relpath_posix(path, start=os.path.curdir):
+    """Return a relative version of a path"""
+
+    if not path:
+        raise ValueError("no path specified")
+
+    start_list = os.path.abspath(start).split(os.path.sep)
+    path_list = os.path.abspath(path).split(os.path.sep)
+
+    # Work out how much of the filepath is shared by start and path.
+    i = len(os.path.commonprefix([start_list, path_list]))
+
+    rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
+    if not rel_list:
+        return os.path.curdir
+    return os.path.join(*rel_list)
+
+if os.path is sys.modules.get('ntpath'):
+    relpath = _relpath_nt
+else:
+    relpath = _relpath_posix

+ 322 - 0
django/utils/unittest/loader.py

@@ -0,0 +1,322 @@
+"""Loading unittests."""
+
+import os
+import re
+import sys
+import traceback
+import types
+import unittest
+
+from fnmatch import fnmatch
+
+from django.utils.unittest import case, suite
+
+try:
+    from os.path import relpath
+except ImportError:
+    from django.utils.unittest.compatibility import relpath
+
+__unittest = True
+
+
+def _CmpToKey(mycmp):
+    'Convert a cmp= function into a key= function'
+    class K(object):
+        def __init__(self, obj):
+            self.obj = obj
+        def __lt__(self, other):
+            return mycmp(self.obj, other.obj) == -1
+    return K
+
+
+# what about .pyc or .pyo (etc)
+# we would need to avoid loading the same tests multiple times
+# from '.py', '.pyc' *and* '.pyo'
+VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE)
+
+
+def _make_failed_import_test(name, suiteClass):
+    message = 'Failed to import test module: %s' % name
+    if hasattr(traceback, 'format_exc'):
+        # Python 2.3 compatibility
+        # format_exc returns two frames of discover.py as well
+        message += '\n%s' % traceback.format_exc()
+    return _make_failed_test('ModuleImportFailure', name, ImportError(message),
+                             suiteClass)
+
+def _make_failed_load_tests(name, exception, suiteClass):
+    return _make_failed_test('LoadTestsFailure', name, exception, suiteClass)
+
+def _make_failed_test(classname, methodname, exception, suiteClass):
+    def testFailure(self):
+        raise exception
+    attrs = {methodname: testFailure}
+    TestClass = type(classname, (case.TestCase,), attrs)
+    return suiteClass((TestClass(methodname),))
+
+
+class TestLoader(unittest.TestLoader):
+    """
+    This class is responsible for loading tests according to various criteria
+    and returning them wrapped in a TestSuite
+    """
+    testMethodPrefix = 'test'
+    sortTestMethodsUsing = cmp
+    suiteClass = suite.TestSuite
+    _top_level_dir = None
+
+    def loadTestsFromTestCase(self, testCaseClass):
+        """Return a suite of all tests cases contained in testCaseClass"""
+        if issubclass(testCaseClass, suite.TestSuite):
+            raise TypeError("Test cases should not be derived from TestSuite."
+                            " Maybe you meant to derive from TestCase?")
+        testCaseNames = self.getTestCaseNames(testCaseClass)
+        if not testCaseNames and hasattr(testCaseClass, 'runTest'):
+            testCaseNames = ['runTest']
+        loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
+        return loaded_suite
+
+    def loadTestsFromModule(self, module, use_load_tests=True):
+        """Return a suite of all tests cases contained in the given module"""
+        tests = []
+        for name in dir(module):
+            obj = getattr(module, name)
+            if isinstance(obj, type) and issubclass(obj, unittest.TestCase):
+                tests.append(self.loadTestsFromTestCase(obj))
+
+        load_tests = getattr(module, 'load_tests', None)
+        tests = self.suiteClass(tests)
+        if use_load_tests and load_tests is not None:
+            try:
+                return load_tests(self, tests, None)
+            except Exception, e:
+                return _make_failed_load_tests(module.__name__, e,
+                                               self.suiteClass)
+        return tests
+
+    def loadTestsFromName(self, name, module=None):
+        """Return a suite of all tests cases given a string specifier.
+
+        The name may resolve either to a module, a test case class, a
+        test method within a test case class, or a callable object which
+        returns a TestCase or TestSuite instance.
+
+        The method optionally resolves the names relative to a given module.
+        """
+        parts = name.split('.')
+        if module is None:
+            parts_copy = parts[:]
+            while parts_copy:
+                try:
+                    module = __import__('.'.join(parts_copy))
+                    break
+                except ImportError:
+                    del parts_copy[-1]
+                    if not parts_copy:
+                        raise
+            parts = parts[1:]
+        obj = module
+        for part in parts:
+            parent, obj = obj, getattr(obj, part)
+
+        if isinstance(obj, types.ModuleType):
+            return self.loadTestsFromModule(obj)
+        elif isinstance(obj, type) and issubclass(obj, unittest.TestCase):
+            return self.loadTestsFromTestCase(obj)
+        elif (isinstance(obj, types.UnboundMethodType) and
+              isinstance(parent, type) and
+              issubclass(parent, case.TestCase)):
+            return self.suiteClass([parent(obj.__name__)])
+        elif isinstance(obj, unittest.TestSuite):
+            return obj
+        elif hasattr(obj, '__call__'):
+            test = obj()
+            if isinstance(test, unittest.TestSuite):
+                return test
+            elif isinstance(test, unittest.TestCase):
+                return self.suiteClass([test])
+            else:
+                raise TypeError("calling %s returned %s, not a test" %
+                                (obj, test))
+        else:
+            raise TypeError("don't know how to make test from: %s" % obj)
+
+    def loadTestsFromNames(self, names, module=None):
+        """Return a suite of all tests cases found using the given sequence
+        of string specifiers. See 'loadTestsFromName()'.
+        """
+        suites = [self.loadTestsFromName(name, module) for name in names]
+        return self.suiteClass(suites)
+
+    def getTestCaseNames(self, testCaseClass):
+        """Return a sorted sequence of method names found within testCaseClass
+        """
+        def isTestMethod(attrname, testCaseClass=testCaseClass,
+                         prefix=self.testMethodPrefix):
+            return attrname.startswith(prefix) and \
+                hasattr(getattr(testCaseClass, attrname), '__call__')
+        testFnNames = filter(isTestMethod, dir(testCaseClass))
+        if self.sortTestMethodsUsing:
+            testFnNames.sort(key=_CmpToKey(self.sortTestMethodsUsing))
+        return testFnNames
+
+    def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
+        """Find and return all test modules from the specified start
+        directory, recursing into subdirectories to find them. Only test files
+        that match the pattern will be loaded. (Using shell style pattern
+        matching.)
+
+        All test modules must be importable from the top level of the project.
+        If the start directory is not the top level directory then the top
+        level directory must be specified separately.
+
+        If a test package name (directory with '__init__.py') matches the
+        pattern then the package will be checked for a 'load_tests' function. If
+        this exists then it will be called with loader, tests, pattern.
+
+        If load_tests exists then discovery does  *not* recurse into the package,
+        load_tests is responsible for loading all tests in the package.
+
+        The pattern is deliberately not stored as a loader attribute so that
+        packages can continue discovery themselves. top_level_dir is stored so
+        load_tests does not need to pass this argument in to loader.discover().
+        """
+        set_implicit_top = False
+        if top_level_dir is None and self._top_level_dir is not None:
+            # make top_level_dir optional if called from load_tests in a package
+            top_level_dir = self._top_level_dir
+        elif top_level_dir is None:
+            set_implicit_top = True
+            top_level_dir = start_dir
+
+        top_level_dir = os.path.abspath(top_level_dir)
+
+        if not top_level_dir in sys.path:
+            # all test modules must be importable from the top level directory
+            # should we *unconditionally* put the start directory in first
+            # in sys.path to minimise likelihood of conflicts between installed
+            # modules and development versions?
+            sys.path.insert(0, top_level_dir)
+        self._top_level_dir = top_level_dir
+
+        is_not_importable = False
+        if os.path.isdir(os.path.abspath(start_dir)):
+            start_dir = os.path.abspath(start_dir)
+            if start_dir != top_level_dir:
+                is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))
+        else:
+            # support for discovery from dotted module names
+            try:
+                __import__(start_dir)
+            except ImportError:
+                is_not_importable = True
+            else:
+                the_module = sys.modules[start_dir]
+                top_part = start_dir.split('.')[0]
+                start_dir = os.path.abspath(os.path.dirname((the_module.__file__)))
+                if set_implicit_top:
+                    self._top_level_dir = os.path.abspath(os.path.dirname(os.path.dirname(sys.modules[top_part].__file__)))
+                    sys.path.remove(top_level_dir)
+
+        if is_not_importable:
+            raise ImportError('Start directory is not importable: %r' % start_dir)
+
+        tests = list(self._find_tests(start_dir, pattern))
+        return self.suiteClass(tests)
+
+    def _get_name_from_path(self, path):
+        path = os.path.splitext(os.path.normpath(path))[0]
+
+        _relpath = relpath(path, self._top_level_dir)
+        assert not os.path.isabs(_relpath), "Path must be within the project"
+        assert not _relpath.startswith('..'), "Path must be within the project"
+
+        name = _relpath.replace(os.path.sep, '.')
+        return name
+
+    def _get_module_from_name(self, name):
+        __import__(name)
+        return sys.modules[name]
+
+    def _match_path(self, path, full_path, pattern):
+        # override this method to use alternative matching strategy
+        return fnmatch(path, pattern)
+
+    def _find_tests(self, start_dir, pattern):
+        """Used by discovery. Yields test suites it loads."""
+        paths = os.listdir(start_dir)
+
+        for path in paths:
+            full_path = os.path.join(start_dir, path)
+            if os.path.isfile(full_path):
+                if not VALID_MODULE_NAME.match(path):
+                    # valid Python identifiers only
+                    continue
+                if not self._match_path(path, full_path, pattern):
+                    continue
+                # if the test file matches, load it
+                name = self._get_name_from_path(full_path)
+                try:
+                    module = self._get_module_from_name(name)
+                except:
+                    yield _make_failed_import_test(name, self.suiteClass)
+                else:
+                    mod_file = os.path.abspath(getattr(module, '__file__', full_path))
+                    realpath = os.path.splitext(mod_file)[0]
+                    fullpath_noext = os.path.splitext(full_path)[0]
+                    if realpath.lower() != fullpath_noext.lower():
+                        module_dir = os.path.dirname(realpath)
+                        mod_name = os.path.splitext(os.path.basename(full_path))[0]
+                        expected_dir = os.path.dirname(full_path)
+                        msg = ("%r module incorrectly imported from %r. Expected %r. "
+                               "Is this module globally installed?")
+                        raise ImportError(msg % (mod_name, module_dir, expected_dir))
+                    yield self.loadTestsFromModule(module)
+            elif os.path.isdir(full_path):
+                if not os.path.isfile(os.path.join(full_path, '__init__.py')):
+                    continue
+
+                load_tests = None
+                tests = None
+                if fnmatch(path, pattern):
+                    # only check load_tests if the package directory itself matches the filter
+                    name = self._get_name_from_path(full_path)
+                    package = self._get_module_from_name(name)
+                    load_tests = getattr(package, 'load_tests', None)
+                    tests = self.loadTestsFromModule(package, use_load_tests=False)
+
+                if load_tests is None:
+                    if tests is not None:
+                        # tests loaded from package file
+                        yield tests
+                    # recurse into the package
+                    for test in self._find_tests(full_path, pattern):
+                        yield test
+                else:
+                    try:
+                        yield load_tests(self, tests, pattern)
+                    except Exception, e:
+                        yield _make_failed_load_tests(package.__name__, e,
+                                                      self.suiteClass)
+
+defaultTestLoader = TestLoader()
+
+
+def _makeLoader(prefix, sortUsing, suiteClass=None):
+    loader = TestLoader()
+    loader.sortTestMethodsUsing = sortUsing
+    loader.testMethodPrefix = prefix
+    if suiteClass:
+        loader.suiteClass = suiteClass
+    return loader
+
+def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
+    return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
+
+def makeSuite(testCaseClass, prefix='test', sortUsing=cmp,
+              suiteClass=suite.TestSuite):
+    return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
+
+def findTestCases(module, prefix='test', sortUsing=cmp,
+                  suiteClass=suite.TestSuite):
+    return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module)

+ 241 - 0
django/utils/unittest/main.py

@@ -0,0 +1,241 @@
+"""Unittest main program"""
+
+import sys
+import os
+import types
+
+from django.utils.unittest import loader, runner
+try:
+    from django.utils.unittest.signals import installHandler
+except ImportError:
+    installHandler = None
+
+__unittest = True
+
+FAILFAST     = "  -f, --failfast   Stop on first failure\n"
+CATCHBREAK   = "  -c, --catch      Catch control-C and display results\n"
+BUFFEROUTPUT = "  -b, --buffer     Buffer stdout and stderr during test runs\n"
+
+USAGE_AS_MAIN = """\
+Usage: %(progName)s [options] [tests]
+
+Options:
+  -h, --help       Show this message
+  -v, --verbose    Verbose output
+  -q, --quiet      Minimal output
+%(failfast)s%(catchbreak)s%(buffer)s
+Examples:
+  %(progName)s test_module                       - run tests from test_module
+  %(progName)s test_module.TestClass             - run tests from
+                                                   test_module.TestClass
+  %(progName)s test_module.TestClass.test_method - run specified test method
+
+[tests] can be a list of any number of test modules, classes and test
+methods.
+
+Alternative Usage: %(progName)s discover [options]
+
+Options:
+  -v, --verbose    Verbose output
+%(failfast)s%(catchbreak)s%(buffer)s  -s directory     Directory to start discovery ('.' default)
+  -p pattern       Pattern to match test files ('test*.py' default)
+  -t directory     Top level directory of project (default to
+                   start directory)
+
+For test discovery all test modules must be importable from the top
+level directory of the project.
+"""
+
+USAGE_FROM_MODULE = """\
+Usage: %(progName)s [options] [test] [...]
+
+Options:
+  -h, --help       Show this message
+  -v, --verbose    Verbose output
+  -q, --quiet      Minimal output
+%(failfast)s%(catchbreak)s%(buffer)s
+Examples:
+  %(progName)s                               - run default set of tests
+  %(progName)s MyTestSuite                   - run suite 'MyTestSuite'
+  %(progName)s MyTestCase.testSomething      - run MyTestCase.testSomething
+  %(progName)s MyTestCase                    - run all 'test*' test methods
+                                               in MyTestCase
+"""
+
+
+class TestProgram(object):
+    """A command-line program that runs a set of tests; this is primarily
+       for making test modules conveniently executable.
+    """
+    USAGE = USAGE_FROM_MODULE
+
+    # defaults for testing
+    failfast = catchbreak = buffer = progName = None
+
+    def __init__(self, module='__main__', defaultTest=None,
+                 argv=None, testRunner=None,
+                 testLoader=loader.defaultTestLoader, exit=True,
+                 verbosity=1, failfast=None, catchbreak=None, buffer=None):
+        if isinstance(module, basestring):
+            self.module = __import__(module)
+            for part in module.split('.')[1:]:
+                self.module = getattr(self.module, part)
+        else:
+            self.module = module
+        if argv is None:
+            argv = sys.argv
+
+        self.exit = exit
+        self.verbosity = verbosity
+        self.failfast = failfast
+        self.catchbreak = catchbreak
+        self.buffer = buffer
+        self.defaultTest = defaultTest
+        self.testRunner = testRunner
+        self.testLoader = testLoader
+        self.progName = os.path.basename(argv[0])
+        self.parseArgs(argv)
+        self.runTests()
+
+    def usageExit(self, msg=None):
+        if msg:
+            print msg
+        usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
+                 'buffer': ''}
+        if self.failfast != False:
+            usage['failfast'] = FAILFAST
+        if self.catchbreak != False and installHandler is not None:
+            usage['catchbreak'] = CATCHBREAK
+        if self.buffer != False:
+            usage['buffer'] = BUFFEROUTPUT
+        print self.USAGE % usage
+        sys.exit(2)
+
+    def parseArgs(self, argv):
+        if len(argv) > 1 and argv[1].lower() == 'discover':
+            self._do_discovery(argv[2:])
+            return
+
+        import getopt
+        long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer']
+        try:
+            options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts)
+            for opt, value in options:
+                if opt in ('-h','-H','--help'):
+                    self.usageExit()
+                if opt in ('-q','--quiet'):
+                    self.verbosity = 0
+                if opt in ('-v','--verbose'):
+                    self.verbosity = 2
+                if opt in ('-f','--failfast'):
+                    if self.failfast is None:
+                        self.failfast = True
+                    # Should this raise an exception if -f is not valid?
+                if opt in ('-c','--catch'):
+                    if self.catchbreak is None and installHandler is not None:
+                        self.catchbreak = True
+                    # Should this raise an exception if -c is not valid?
+                if opt in ('-b','--buffer'):
+                    if self.buffer is None:
+                        self.buffer = True
+                    # Should this raise an exception if -b is not valid?
+            if len(args) == 0 and self.defaultTest is None:
+                # createTests will load tests from self.module
+                self.testNames = None
+            elif len(args) > 0:
+                self.testNames = args
+                if __name__ == '__main__':
+                    # to support python -m unittest ...
+                    self.module = None
+            else:
+                self.testNames = (self.defaultTest,)
+            self.createTests()
+        except getopt.error, msg:
+            self.usageExit(msg)
+
+    def createTests(self):
+        if self.testNames is None:
+            self.test = self.testLoader.loadTestsFromModule(self.module)
+        else:
+            self.test = self.testLoader.loadTestsFromNames(self.testNames,
+                                                           self.module)
+
+    def _do_discovery(self, argv, Loader=loader.TestLoader):
+        # handle command line args for test discovery
+        self.progName = '%s discover' % self.progName
+        import optparse
+        parser = optparse.OptionParser()
+        parser.prog = self.progName
+        parser.add_option('-v', '--verbose', dest='verbose', default=False,
+                          help='Verbose output', action='store_true')
+        if self.failfast != False:
+            parser.add_option('-f', '--failfast', dest='failfast', default=False,
+                              help='Stop on first fail or error',
+                              action='store_true')
+        if self.catchbreak != False and installHandler is not None:
+            parser.add_option('-c', '--catch', dest='catchbreak', default=False,
+                              help='Catch ctrl-C and display results so far',
+                              action='store_true')
+        if self.buffer != False:
+            parser.add_option('-b', '--buffer', dest='buffer', default=False,
+                              help='Buffer stdout and stderr during tests',
+                              action='store_true')
+        parser.add_option('-s', '--start-directory', dest='start', default='.',
+                          help="Directory to start discovery ('.' default)")
+        parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
+                          help="Pattern to match tests ('test*.py' default)")
+        parser.add_option('-t', '--top-level-directory', dest='top', default=None,
+                          help='Top level directory of project (defaults to start directory)')
+
+        options, args = parser.parse_args(argv)
+        if len(args) > 3:
+            self.usageExit()
+
+        for name, value in zip(('start', 'pattern', 'top'), args):
+            setattr(options, name, value)
+
+        # only set options from the parsing here
+        # if they weren't set explicitly in the constructor
+        if self.failfast is None:
+            self.failfast = options.failfast
+        if self.catchbreak is None and installHandler is not None:
+            self.catchbreak = options.catchbreak
+        if self.buffer is None:
+            self.buffer = options.buffer
+
+        if options.verbose:
+            self.verbosity = 2
+
+        start_dir = options.start
+        pattern = options.pattern
+        top_level_dir = options.top
+
+        loader = Loader()
+        self.test = loader.discover(start_dir, pattern, top_level_dir)
+
+    def runTests(self):
+        if self.catchbreak:
+            installHandler()
+        if self.testRunner is None:
+            self.testRunner = runner.TextTestRunner
+        if isinstance(self.testRunner, (type, types.ClassType)):
+            try:
+                testRunner = self.testRunner(verbosity=self.verbosity,
+                                             failfast=self.failfast,
+                                             buffer=self.buffer)
+            except TypeError:
+                # didn't accept the verbosity, buffer or failfast arguments
+                testRunner = self.testRunner()
+        else:
+            # it is assumed to be a TestRunner instance
+            testRunner = self.testRunner
+        self.result = testRunner.run(self.test)
+        if self.exit:
+            sys.exit(not self.result.wasSuccessful())
+
+main = TestProgram
+
+def main_():
+    TestProgram.USAGE = USAGE_AS_MAIN
+    main(module=None)
+

+ 183 - 0
django/utils/unittest/result.py

@@ -0,0 +1,183 @@
+"""Test result object"""
+
+import sys
+import traceback
+import unittest
+
+from StringIO import StringIO
+
+from django.utils.unittest import util
+from django.utils.unittest.compatibility import wraps
+
+__unittest = True
+
+def failfast(method):
+    @wraps(method)
+    def inner(self, *args, **kw):
+        if getattr(self, 'failfast', False):
+            self.stop()
+        return method(self, *args, **kw)
+    return inner
+
+
+STDOUT_LINE = '\nStdout:\n%s'
+STDERR_LINE = '\nStderr:\n%s'
+
+class TestResult(unittest.TestResult):
+    """Holder for test result information.
+
+    Test results are automatically managed by the TestCase and TestSuite
+    classes, and do not need to be explicitly manipulated by writers of tests.
+
+    Each instance holds the total number of tests run, and collections of
+    failures and errors that occurred among those test runs. The collections
+    contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
+    formatted traceback of the error that occurred.
+    """
+    _previousTestClass = None
+    _moduleSetUpFailed = False
+
+    def __init__(self):
+        self.failfast = False
+        self.failures = []
+        self.errors = []
+        self.testsRun = 0
+        self.skipped = []
+        self.expectedFailures = []
+        self.unexpectedSuccesses = []
+        self.shouldStop = False
+        self.buffer = False
+        self._stdout_buffer = None
+        self._stderr_buffer = None
+        self._original_stdout = sys.stdout
+        self._original_stderr = sys.stderr
+        self._mirrorOutput = False
+
+    def startTest(self, test):
+        "Called when the given test is about to be run"
+        self.testsRun += 1
+        self._mirrorOutput = False
+        if self.buffer:
+            if self._stderr_buffer is None:
+                self._stderr_buffer = StringIO()
+                self._stdout_buffer = StringIO()
+            sys.stdout = self._stdout_buffer
+            sys.stderr = self._stderr_buffer
+
+    def startTestRun(self):
+        """Called once before any tests are executed.
+
+        See startTest for a method called before each test.
+        """
+
+    def stopTest(self, test):
+        """Called when the given test has been run"""
+        if self.buffer:
+            if self._mirrorOutput:
+                output = sys.stdout.getvalue()
+                error = sys.stderr.getvalue()
+                if output:
+                    if not output.endswith('\n'):
+                        output += '\n'
+                    self._original_stdout.write(STDOUT_LINE % output)
+                if error:
+                    if not error.endswith('\n'):
+                        error += '\n'
+                    self._original_stderr.write(STDERR_LINE % error)
+
+            sys.stdout = self._original_stdout
+            sys.stderr = self._original_stderr
+            self._stdout_buffer.seek(0)
+            self._stdout_buffer.truncate()
+            self._stderr_buffer.seek(0)
+            self._stderr_buffer.truncate()
+        self._mirrorOutput = False
+
+
+    def stopTestRun(self):
+        """Called once after all tests are executed.
+
+        See stopTest for a method called after each test.
+        """
+
+    @failfast
+    def addError(self, test, err):
+        """Called when an error has occurred. 'err' is a tuple of values as
+        returned by sys.exc_info().
+        """
+        self.errors.append((test, self._exc_info_to_string(err, test)))
+        self._mirrorOutput = True
+
+    @failfast
+    def addFailure(self, test, err):
+        """Called when an error has occurred. 'err' is a tuple of values as
+        returned by sys.exc_info()."""
+        self.failures.append((test, self._exc_info_to_string(err, test)))
+        self._mirrorOutput = True
+
+    def addSuccess(self, test):
+        "Called when a test has completed successfully"
+        pass
+
+    def addSkip(self, test, reason):
+        """Called when a test is skipped."""
+        self.skipped.append((test, reason))
+
+    def addExpectedFailure(self, test, err):
+        """Called when an expected failure/error occured."""
+        self.expectedFailures.append(
+            (test, self._exc_info_to_string(err, test)))
+
+    @failfast
+    def addUnexpectedSuccess(self, test):
+        """Called when a test was expected to fail, but succeed."""
+        self.unexpectedSuccesses.append(test)
+
+    def wasSuccessful(self):
+        "Tells whether or not this result was a success"
+        return (len(self.failures) + len(self.errors) == 0)
+
+    def stop(self):
+        "Indicates that the tests should be aborted"
+        self.shouldStop = True
+
+    def _exc_info_to_string(self, err, test):
+        """Converts a sys.exc_info()-style tuple of values into a string."""
+        exctype, value, tb = err
+        # Skip test runner traceback levels
+        while tb and self._is_relevant_tb_level(tb):
+            tb = tb.tb_next
+        if exctype is test.failureException:
+            # Skip assert*() traceback levels
+            length = self._count_relevant_tb_levels(tb)
+            msgLines = traceback.format_exception(exctype, value, tb, length)
+        else:
+            msgLines = traceback.format_exception(exctype, value, tb)
+
+        if self.buffer:
+            output = sys.stdout.getvalue()
+            error = sys.stderr.getvalue()
+            if output:
+                if not output.endswith('\n'):
+                    output += '\n'
+                msgLines.append(STDOUT_LINE % output)
+            if error:
+                if not error.endswith('\n'):
+                    error += '\n'
+                msgLines.append(STDERR_LINE % error)
+        return ''.join(msgLines)
+
+    def _is_relevant_tb_level(self, tb):
+        return '__unittest' in tb.tb_frame.f_globals
+
+    def _count_relevant_tb_levels(self, tb):
+        length = 0
+        while tb and not self._is_relevant_tb_level(tb):
+            length += 1
+            tb = tb.tb_next
+        return length
+
+    def __repr__(self):
+        return "<%s run=%i errors=%i failures=%i>" % \
+               (util.strclass(self.__class__), self.testsRun, len(self.errors),
+                len(self.failures))

+ 206 - 0
django/utils/unittest/runner.py

@@ -0,0 +1,206 @@
+"""Running tests"""
+
+import sys
+import time
+import unittest
+
+from django.utils.unittest import result
+
+try:
+    from django.utils.unittest.signals import registerResult
+except ImportError:
+    def registerResult(_):
+        pass
+
+__unittest = True
+
+
+class _WritelnDecorator(object):
+    """Used to decorate file-like objects with a handy 'writeln' method"""
+    def __init__(self,stream):
+        self.stream = stream
+
+    def __getattr__(self, attr):
+        if attr in ('stream', '__getstate__'):
+            raise AttributeError(attr)
+        return getattr(self.stream,attr)
+
+    def writeln(self, arg=None):
+        if arg:
+            self.write(arg)
+        self.write('\n') # text-mode streams translate to \r\n if needed
+
+
+class TextTestResult(result.TestResult):
+    """A test result class that can print formatted text results to a stream.
+
+    Used by TextTestRunner.
+    """
+    separator1 = '=' * 70
+    separator2 = '-' * 70
+
+    def __init__(self, stream, descriptions, verbosity):
+        super(TextTestResult, self).__init__()
+        self.stream = stream
+        self.showAll = verbosity > 1
+        self.dots = verbosity == 1
+        self.descriptions = descriptions
+
+    def getDescription(self, test):
+        doc_first_line = test.shortDescription()
+        if self.descriptions and doc_first_line:
+            return '\n'.join((str(test), doc_first_line))
+        else:
+            return str(test)
+
+    def startTest(self, test):
+        super(TextTestResult, self).startTest(test)
+        if self.showAll:
+            self.stream.write(self.getDescription(test))
+            self.stream.write(" ... ")
+            self.stream.flush()
+
+    def addSuccess(self, test):
+        super(TextTestResult, self).addSuccess(test)
+        if self.showAll:
+            self.stream.writeln("ok")
+        elif self.dots:
+            self.stream.write('.')
+            self.stream.flush()
+
+    def addError(self, test, err):
+        super(TextTestResult, self).addError(test, err)
+        if self.showAll:
+            self.stream.writeln("ERROR")
+        elif self.dots:
+            self.stream.write('E')
+            self.stream.flush()
+
+    def addFailure(self, test, err):
+        super(TextTestResult, self).addFailure(test, err)
+        if self.showAll:
+            self.stream.writeln("FAIL")
+        elif self.dots:
+            self.stream.write('F')
+            self.stream.flush()
+
+    def addSkip(self, test, reason):
+        super(TextTestResult, self).addSkip(test, reason)
+        if self.showAll:
+            self.stream.writeln("skipped %r" % (reason,))
+        elif self.dots:
+            self.stream.write("s")
+            self.stream.flush()
+
+    def addExpectedFailure(self, test, err):
+        super(TextTestResult, self).addExpectedFailure(test, err)
+        if self.showAll:
+            self.stream.writeln("expected failure")
+        elif self.dots:
+            self.stream.write("x")
+            self.stream.flush()
+
+    def addUnexpectedSuccess(self, test):
+        super(TextTestResult, self).addUnexpectedSuccess(test)
+        if self.showAll:
+            self.stream.writeln("unexpected success")
+        elif self.dots:
+            self.stream.write("u")
+            self.stream.flush()
+
+    def printErrors(self):
+        if self.dots or self.showAll:
+            self.stream.writeln()
+        self.printErrorList('ERROR', self.errors)
+        self.printErrorList('FAIL', self.failures)
+
+    def printErrorList(self, flavour, errors):
+        for test, err in errors:
+            self.stream.writeln(self.separator1)
+            self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
+            self.stream.writeln(self.separator2)
+            self.stream.writeln("%s" % err)
+
+    def stopTestRun(self):
+        super(TextTestResult, self).stopTestRun()
+        self.printErrors()
+
+
+class TextTestRunner(unittest.TextTestRunner):
+    """A test runner class that displays results in textual form.
+
+    It prints out the names of tests as they are run, errors as they
+    occur, and a summary of the results at the end of the test run.
+    """
+    resultclass = TextTestResult
+
+    def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
+                    failfast=False, buffer=False, resultclass=None):
+        self.stream = _WritelnDecorator(stream)
+        self.descriptions = descriptions
+        self.verbosity = verbosity
+        self.failfast = failfast
+        self.buffer = buffer
+        if resultclass is not None:
+            self.resultclass = resultclass
+
+    def _makeResult(self):
+        return self.resultclass(self.stream, self.descriptions, self.verbosity)
+
+    def run(self, test):
+        "Run the given test case or test suite."
+        result = self._makeResult()
+        result.failfast = self.failfast
+        result.buffer = self.buffer
+        registerResult(result)
+
+        startTime = time.time()
+        startTestRun = getattr(result, 'startTestRun', None)
+        if startTestRun is not None:
+            startTestRun()
+        try:
+            test(result)
+        finally:
+            stopTestRun = getattr(result, 'stopTestRun', None)
+            if stopTestRun is not None:
+                stopTestRun()
+            else:
+                result.printErrors()
+        stopTime = time.time()
+        timeTaken = stopTime - startTime
+        if hasattr(result, 'separator2'):
+            self.stream.writeln(result.separator2)
+        run = result.testsRun
+        self.stream.writeln("Ran %d test%s in %.3fs" %
+                            (run, run != 1 and "s" or "", timeTaken))
+        self.stream.writeln()
+
+        expectedFails = unexpectedSuccesses = skipped = 0
+        try:
+            results = map(len, (result.expectedFailures,
+                                result.unexpectedSuccesses,
+                                result.skipped))
+            expectedFails, unexpectedSuccesses, skipped = results
+        except AttributeError:
+            pass
+        infos = []
+        if not result.wasSuccessful():
+            self.stream.write("FAILED")
+            failed, errored = map(len, (result.failures, result.errors))
+            if failed:
+                infos.append("failures=%d" % failed)
+            if errored:
+                infos.append("errors=%d" % errored)
+        else:
+            self.stream.write("OK")
+        if skipped:
+            infos.append("skipped=%d" % skipped)
+        if expectedFails:
+            infos.append("expected failures=%d" % expectedFails)
+        if unexpectedSuccesses:
+            infos.append("unexpected successes=%d" % unexpectedSuccesses)
+        if infos:
+            self.stream.writeln(" (%s)" % (", ".join(infos),))
+        else:
+            self.stream.write("\n")
+        return result

+ 57 - 0
django/utils/unittest/signals.py

@@ -0,0 +1,57 @@
+import signal
+import weakref
+
+from django.utils.unittest.compatibility import wraps
+
+__unittest = True
+
+
+class _InterruptHandler(object):
+    def __init__(self, default_handler):
+        self.called = False
+        self.default_handler = default_handler
+
+    def __call__(self, signum, frame):
+        installed_handler = signal.getsignal(signal.SIGINT)
+        if installed_handler is not self:
+            # if we aren't the installed handler, then delegate immediately
+            # to the default handler
+            self.default_handler(signum, frame)
+
+        if self.called:
+            self.default_handler(signum, frame)
+        self.called = True
+        for result in _results.keys():
+            result.stop()
+
+_results = weakref.WeakKeyDictionary()
+def registerResult(result):
+    _results[result] = 1
+
+def removeResult(result):
+    return bool(_results.pop(result, None))
+
+_interrupt_handler = None
+def installHandler():
+    global _interrupt_handler
+    if _interrupt_handler is None:
+        default_handler = signal.getsignal(signal.SIGINT)
+        _interrupt_handler = _InterruptHandler(default_handler)
+        signal.signal(signal.SIGINT, _interrupt_handler)
+
+
+def removeHandler(method=None):
+    if method is not None:
+        @wraps(method)
+        def inner(*args, **kwargs):
+            initial = signal.getsignal(signal.SIGINT)
+            removeHandler()
+            try:
+                return method(*args, **kwargs)
+            finally:
+                signal.signal(signal.SIGINT, initial)
+        return inner
+
+    global _interrupt_handler
+    if _interrupt_handler is not None:
+        signal.signal(signal.SIGINT, _interrupt_handler.default_handler)

+ 287 - 0
django/utils/unittest/suite.py

@@ -0,0 +1,287 @@
+"""TestSuite"""
+
+import sys
+import unittest
+from django.utils.unittest import case, util
+
+__unittest = True
+
+
+class BaseTestSuite(unittest.TestSuite):
+    """A simple test suite that doesn't provide class or module shared fixtures.
+    """
+    def __init__(self, tests=()):
+        self._tests = []
+        self.addTests(tests)
+
+    def __repr__(self):
+        return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+        return list(self) == list(other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # Can't guarantee hash invariant, so flag as unhashable
+    __hash__ = None
+
+    def __iter__(self):
+        return iter(self._tests)
+
+    def countTestCases(self):
+        cases = 0
+        for test in self:
+            cases += test.countTestCases()
+        return cases
+
+    def addTest(self, test):
+        # sanity checks
+        if not hasattr(test, '__call__'):
+            raise TypeError("%r is not callable" % (repr(test),))
+        if isinstance(test, type) and issubclass(test,
+                                                 (case.TestCase, TestSuite)):
+            raise TypeError("TestCases and TestSuites must be instantiated "
+                            "before passing them to addTest()")
+        self._tests.append(test)
+
+    def addTests(self, tests):
+        if isinstance(tests, basestring):
+            raise TypeError("tests must be an iterable of tests, not a string")
+        for test in tests:
+            self.addTest(test)
+
+    def run(self, result):
+        for test in self:
+            if result.shouldStop:
+                break
+            test(result)
+        return result
+
+    def __call__(self, *args, **kwds):
+        return self.run(*args, **kwds)
+
+    def debug(self):
+        """Run the tests without collecting errors in a TestResult"""
+        for test in self:
+            test.debug()
+
+
+class TestSuite(BaseTestSuite):
+    """A test suite is a composite test consisting of a number of TestCases.
+
+    For use, create an instance of TestSuite, then add test case instances.
+    When all tests have been added, the suite can be passed to a test
+    runner, such as TextTestRunner. It will run the individual test cases
+    in the order in which they were added, aggregating the results. When
+    subclassing, do not forget to call the base class constructor.
+    """
+
+
+    def run(self, result):
+        self._wrapped_run(result)
+        self._tearDownPreviousClass(None, result)
+        self._handleModuleTearDown(result)
+        return result
+
+    def debug(self):
+        """Run the tests without collecting errors in a TestResult"""
+        debug = _DebugResult()
+        self._wrapped_run(debug, True)
+        self._tearDownPreviousClass(None, debug)
+        self._handleModuleTearDown(debug)
+
+    ################################
+    # private methods
+    def _wrapped_run(self, result, debug=False):
+        for test in self:
+            if result.shouldStop:
+                break
+
+            if _isnotsuite(test):
+                self._tearDownPreviousClass(test, result)
+                self._handleModuleFixture(test, result)
+                self._handleClassSetUp(test, result)
+                result._previousTestClass = test.__class__
+
+                if (getattr(test.__class__, '_classSetupFailed', False) or
+                    getattr(result, '_moduleSetUpFailed', False)):
+                    continue
+
+            if hasattr(test, '_wrapped_run'):
+                test._wrapped_run(result, debug)
+            elif not debug:
+                test(result)
+            else:
+                test.debug()
+
+    def _handleClassSetUp(self, test, result):
+        previousClass = getattr(result, '_previousTestClass', None)
+        currentClass = test.__class__
+        if currentClass == previousClass:
+            return
+        if result._moduleSetUpFailed:
+            return
+        if getattr(currentClass, "__unittest_skip__", False):
+            return
+
+        try:
+            currentClass._classSetupFailed = False
+        except TypeError:
+            # test may actually be a function
+            # so its class will be a builtin-type
+            pass
+
+        setUpClass = getattr(currentClass, 'setUpClass', None)
+        if setUpClass is not None:
+            try:
+                setUpClass()
+            except Exception, e:
+                if isinstance(result, _DebugResult):
+                    raise
+                currentClass._classSetupFailed = True
+                className = util.strclass(currentClass)
+                errorName = 'setUpClass (%s)' % className
+                self._addClassOrModuleLevelException(result, e, errorName)
+
+    def _get_previous_module(self, result):
+        previousModule = None
+        previousClass = getattr(result, '_previousTestClass', None)
+        if previousClass is not None:
+            previousModule = previousClass.__module__
+        return previousModule
+
+
+    def _handleModuleFixture(self, test, result):
+        previousModule = self._get_previous_module(result)
+        currentModule = test.__class__.__module__
+        if currentModule == previousModule:
+            return
+
+        self._handleModuleTearDown(result)
+
+
+        result._moduleSetUpFailed = False
+        try:
+            module = sys.modules[currentModule]
+        except KeyError:
+            return
+        setUpModule = getattr(module, 'setUpModule', None)
+        if setUpModule is not None:
+            try:
+                setUpModule()
+            except Exception, e:
+                if isinstance(result, _DebugResult):
+                    raise
+                result._moduleSetUpFailed = True
+                errorName = 'setUpModule (%s)' % currentModule
+                self._addClassOrModuleLevelException(result, e, errorName)
+
+    def _addClassOrModuleLevelException(self, result, exception, errorName):
+        error = _ErrorHolder(errorName)
+        addSkip = getattr(result, 'addSkip', None)
+        if addSkip is not None and isinstance(exception, case.SkipTest):
+            addSkip(error, str(exception))
+        else:
+            result.addError(error, sys.exc_info())
+
+    def _handleModuleTearDown(self, result):
+        previousModule = self._get_previous_module(result)
+        if previousModule is None:
+            return
+        if result._moduleSetUpFailed:
+            return
+
+        try:
+            module = sys.modules[previousModule]
+        except KeyError:
+            return
+
+        tearDownModule = getattr(module, 'tearDownModule', None)
+        if tearDownModule is not None:
+            try:
+                tearDownModule()
+            except Exception, e:
+                if isinstance(result, _DebugResult):
+                    raise
+                errorName = 'tearDownModule (%s)' % previousModule
+                self._addClassOrModuleLevelException(result, e, errorName)
+
+    def _tearDownPreviousClass(self, test, result):
+        previousClass = getattr(result, '_previousTestClass', None)
+        currentClass = test.__class__
+        if currentClass == previousClass:
+            return
+        if getattr(previousClass, '_classSetupFailed', False):
+            return
+        if getattr(result, '_moduleSetUpFailed', False):
+            return
+        if getattr(previousClass, "__unittest_skip__", False):
+            return
+
+        tearDownClass = getattr(previousClass, 'tearDownClass', None)
+        if tearDownClass is not None:
+            try:
+                tearDownClass()
+            except Exception, e:
+                if isinstance(result, _DebugResult):
+                    raise
+                className = util.strclass(previousClass)
+                errorName = 'tearDownClass (%s)' % className
+                self._addClassOrModuleLevelException(result, e, errorName)
+
+
+class _ErrorHolder(object):
+    """
+    Placeholder for a TestCase inside a result. As far as a TestResult
+    is concerned, this looks exactly like a unit test. Used to insert
+    arbitrary errors into a test suite run.
+    """
+    # Inspired by the ErrorHolder from Twisted:
+    # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
+
+    # attribute used by TestResult._exc_info_to_string
+    failureException = None
+
+    def __init__(self, description):
+        self.description = description
+
+    def id(self):
+        return self.description
+
+    def shortDescription(self):
+        return None
+
+    def __repr__(self):
+        return "<ErrorHolder description=%r>" % (self.description,)
+
+    def __str__(self):
+        return self.id()
+
+    def run(self, result):
+        # could call result.addError(...) - but this test-like object
+        # shouldn't be run anyway
+        pass
+
+    def __call__(self, result):
+        return self.run(result)
+
+    def countTestCases(self):
+        return 0
+
+def _isnotsuite(test):
+    "A crude way to tell apart testcases and suites with duck-typing"
+    try:
+        iter(test)
+    except TypeError:
+        return True
+    return False
+
+
+class _DebugResult(object):
+    "Used by the TestSuite to hold previous class when running in debug."
+    _previousTestClass = None
+    _moduleSetUpFailed = False
+    shouldStop = False

+ 99 - 0
django/utils/unittest/util.py

@@ -0,0 +1,99 @@
+"""Various utility functions."""
+
+__unittest = True
+
+
+_MAX_LENGTH = 80
+def safe_repr(obj, short=False):
+    try:
+        result = repr(obj)
+    except Exception:
+        result = object.__repr__(obj)
+    if not short or len(result) < _MAX_LENGTH:
+        return result
+    return result[:_MAX_LENGTH] + ' [truncated]...'
+
+def safe_str(obj):
+    try:
+        return str(obj)
+    except Exception:
+        return object.__str__(obj)
+
+def strclass(cls):
+    return "%s.%s" % (cls.__module__, cls.__name__)
+
+def sorted_list_difference(expected, actual):
+    """Finds elements in only one or the other of two, sorted input lists.
+
+    Returns a two-element tuple of lists.    The first list contains those
+    elements in the "expected" list but not in the "actual" list, and the
+    second contains those elements in the "actual" list but not in the
+    "expected" list.    Duplicate elements in either input list are ignored.
+    """
+    i = j = 0
+    missing = []
+    unexpected = []
+    while True:
+        try:
+            e = expected[i]
+            a = actual[j]
+            if e < a:
+                missing.append(e)
+                i += 1
+                while expected[i] == e:
+                    i += 1
+            elif e > a:
+                unexpected.append(a)
+                j += 1
+                while actual[j] == a:
+                    j += 1
+            else:
+                i += 1
+                try:
+                    while expected[i] == e:
+                        i += 1
+                finally:
+                    j += 1
+                    while actual[j] == a:
+                        j += 1
+        except IndexError:
+            missing.extend(expected[i:])
+            unexpected.extend(actual[j:])
+            break
+    return missing, unexpected
+
+def unorderable_list_difference(expected, actual, ignore_duplicate=False):
+    """Same behavior as sorted_list_difference but
+    for lists of unorderable items (like dicts).
+
+    As it does a linear search per item (remove) it
+    has O(n*n) performance.
+    """
+    missing = []
+    unexpected = []
+    while expected:
+        item = expected.pop()
+        try:
+            actual.remove(item)
+        except ValueError:
+            missing.append(item)
+        if ignore_duplicate:
+            for lst in expected, actual:
+                try:
+                    while True:
+                        lst.remove(item)
+                except ValueError:
+                    pass
+    if ignore_duplicate:
+        while actual:
+            item = actual.pop()
+            unexpected.append(item)
+            try:
+                while True:
+                    actual.remove(item)
+            except ValueError:
+                pass
+        return missing, unexpected
+
+    # anything left in actual is unexpected
+    return missing, actual

+ 28 - 0
docs/releases/1.3.txt

@@ -136,3 +136,31 @@ have been added to Django's own code as well -- most notably, the
 error emails sent on a HTTP 500 server error are now handled as a
 logging activity. See :doc:`the documentation on Django's logging
 interface </topics/logging>` for more details.
+
+``unittest2`` support
+~~~~~~~~~~~~~~~~~~~~~
+
+Python 2.7 introduced some major changes to the unittest library,
+adding some extremely useful features. To ensure that every Django
+project can benefit from these new features, Django ships with a
+copy of unittest2_, a copy of the Python 2.7 unittest library,
+backported for Python 2.4 compatibility.
+
+To access this library, Django provides the
+``django.utils.unittest`` module alias. If you are using Python
+2.7, or you have installed unittest2 locally, Django will mapt the
+alias to the installed version of the unittest library Otherwise,
+Django will use it's own bundled version of unittest2.
+
+To use this alias, simply use::
+
+    from django.utils import unittest
+
+wherever you would historically used::
+
+    import unittest
+
+If you want to continue to use the base unittest libary, you can --
+you just won't get any of the nice new unittest2 features.
+
+.. _unittest2: http://pypi.python.org/pypi/unittest2

+ 51 - 19
docs/topics/testing.txt

@@ -57,8 +57,8 @@ frameworks are:
           class MyFuncTestCase(unittest.TestCase):
               def testBasic(self):
                   a = ['larry', 'curly', 'moe']
-                  self.assertEquals(my_func(a, 0), 'larry')
-                  self.assertEquals(my_func(a, 1), 'curly')
+                  self.assertEqual(my_func(a, 0), 'larry')
+                  self.assertEqual(my_func(a, 1), 'curly')
 
 You can choose the test framework you like, depending on which syntax you
 prefer, or you can mix and match, using one framework for some of your code and
@@ -151,9 +151,38 @@ documentation for doctest`_.
 Writing unit tests
 ------------------
 
-Like doctests, Django's unit tests use a standard library module: unittest_.
-This module uses a different way of defining tests, taking a class-based
-approach.
+Like doctests, Django's unit tests use a Python standard library
+module: unittest_. This module uses a different way of defining tests,
+taking a class-based approach.
+
+.. admonition:: unittest2
+
+    .. versionchanged:: 1.3
+
+    Python 2.7 introduced some major changes to the unittest library,
+    adding some extremely useful features. To ensure that every Django
+    project can benefit from these new features, Django ships with a
+    copy of unittest2_, a copy of the Python 2.7 unittest library,
+    backported for Python 2.4 compatibility.
+
+    To access this library, Django provides the
+    ``django.utils.unittest`` module alias. If you are using Python
+    2.7, or you have installed unittest2 locally, Django will mapt the
+    alias to the installed version of the unittest library Otherwise,
+    Django will use it's own bundled version of unittest2.
+
+    To use this alias, simply use::
+
+        from django.utils import unittest
+
+    wherever you would historically used::
+
+        import unittest
+
+    If you want to continue to use the base unittest libary, you can --
+    you just won't get any of the nice new unittest2 features.
+
+.. _unittest2: http://pypi.python.org/pypi/unittest2
 
 As with doctests, for a given Django application, the test runner looks for
 unit tests in two places:
@@ -168,7 +197,7 @@ unit tests in two places:
 This example ``unittest.TestCase`` subclass is equivalent to the example given
 in the doctest section above::
 
-    import unittest
+    from django.utils import unittest
     from myapp.models import Animal
 
     class AnimalTestCase(unittest.TestCase):
@@ -177,8 +206,8 @@ in the doctest section above::
             self.cat = Animal.objects.create(name="cat", sound="meow")
 
         def testSpeaking(self):
-            self.assertEquals(self.lion.speak(), 'The lion says "roar"')
-            self.assertEquals(self.cat.speak(), 'The cat says "meow"')
+            self.assertEqual(self.lion.speak(), 'The lion says "roar"')
+            self.assertEqual(self.cat.speak(), 'The cat says "meow"')
 
 When you :ref:`run your tests <running-tests>`, the default behavior of the
 test utility is to find all the test cases (that is, subclasses of
@@ -199,6 +228,7 @@ documentation`_.
 .. _standard library unittest documentation: unittest_
 .. _suggested organization: http://docs.python.org/library/unittest.html#organizing-tests
 
+
 Which should I use?
 -------------------
 
@@ -231,6 +261,8 @@ you:
       routines, which give you a high level of control over the environment
       in which your test cases are run.
 
+    * If you're writing tests for Django itself, you should use ``unittest``.
+
 Again, remember that you can use both systems side-by-side (even in the same
 app). In the end, most projects will eventually end up using both. Each shines
 in different circumstances.
@@ -964,7 +996,7 @@ Example
 
 The following is a simple unit test using the test client::
 
-    import unittest
+    from django.utils import unittest
     from django.test.client import Client
 
     class SimpleTest(unittest.TestCase):
@@ -977,10 +1009,10 @@ The following is a simple unit test using the test client::
             response = self.client.get('/customer/details/')
 
             # Check that the response is 200 OK.
-            self.failUnlessEqual(response.status_code, 200)
+            self.assertEqual(response.status_code, 200)
 
             # Check that the rendered context contains 5 customers.
-            self.failUnlessEqual(len(response.context['customers']), 5)
+            self.assertEqual(len(response.context['customers']), 5)
 
 TestCase
 --------
@@ -1061,19 +1093,19 @@ worry about state (such as cookies) carrying over from one test to another.
 
 This means, instead of instantiating a ``Client`` in each test::
 
-    import unittest
+    from django.utils import unittest
     from django.test.client import Client
 
     class SimpleTest(unittest.TestCase):
         def test_details(self):
             client = Client()
             response = client.get('/customer/details/')
-            self.failUnlessEqual(response.status_code, 200)
+            self.assertEqual(response.status_code, 200)
 
         def test_index(self):
             client = Client()
             response = client.get('/customer/index/')
-            self.failUnlessEqual(response.status_code, 200)
+            self.assertEqual(response.status_code, 200)
 
 ...you can just refer to ``self.client``, like so::
 
@@ -1082,11 +1114,11 @@ This means, instead of instantiating a ``Client`` in each test::
     class SimpleTest(TestCase):
         def test_details(self):
             response = self.client.get('/customer/details/')
-            self.failUnlessEqual(response.status_code, 200)
+            self.assertEqual(response.status_code, 200)
 
         def test_index(self):
             response = self.client.get('/customer/index/')
-            self.failUnlessEqual(response.status_code, 200)
+            self.assertEqual(response.status_code, 200)
 
 Customizing the test client
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1265,7 +1297,7 @@ Assertions
     Addded ``msg_prefix`` argument.
 
 As Python's normal ``unittest.TestCase`` class implements assertion methods
-such as ``assertTrue`` and ``assertEquals``, Django's custom ``TestCase`` class
+such as ``assertTrue`` and ``assertEqual``, Django's custom ``TestCase`` class
 provides a number of custom assertion methods that are useful for testing Web
 applications:
 
@@ -1385,10 +1417,10 @@ and contents::
                 fail_silently=False)
 
             # Test that one message has been sent.
-            self.assertEquals(len(mail.outbox), 1)
+            self.assertEqual(len(mail.outbox), 1)
 
             # Verify that the subject of the first message is correct.
-            self.assertEquals(mail.outbox[0].subject, 'Subject here')
+            self.assertEqual(mail.outbox[0].subject, 'Subject here')
 
 As noted :ref:`previously <emptying-test-outbox>`, the test outbox is emptied
 at the start of every test in a Django ``TestCase``. To empty the outbox

+ 3 - 6
tests/modeltests/basic/models.py

@@ -4,7 +4,7 @@
 
 This is a basic model with only two non-primary-key fields.
 """
-from django.db import models, DEFAULT_DB_ALIAS
+from django.db import models, DEFAULT_DB_ALIAS, connection
 
 class Article(models.Model):
     headline = models.CharField(max_length=100, default='Default headline')
@@ -359,9 +359,7 @@ AttributeError: Manager isn't accessible via Article instances
 
 from django.conf import settings
 
-building_docs = getattr(settings, 'BUILDING_DOCS', False)
-
-if building_docs or settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.postgresql':
+if connection.features.supports_microsecond_precision:
     __test__['API_TESTS'] += """
 # In PostgreSQL, microsecond-level precision is available.
 >>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
@@ -369,8 +367,7 @@ if building_docs or settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db
 >>> Article.objects.get(id__exact=9).pub_date
 datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
 """
-
-if building_docs or settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql':
+else:
     __test__['API_TESTS'] += """
 # In MySQL, microsecond-level precision isn't available. You'll lose
 # microsecond-level precision once the data is saved.

+ 10 - 11
tests/modeltests/custom_pk/tests.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 from django.conf import settings
 from django.db import DEFAULT_DB_ALIAS, transaction, IntegrityError
-from django.test import TestCase
+from django.test import TestCase, skipIfDBFeature
 
 from models import Employee, Business, Bar, Foo
 
@@ -168,16 +168,15 @@ class CustomPKTests(TestCase):
         self.assertEqual(f, new_foo),
         self.assertEqual(f.bar, new_bar)
 
-
     # SQLite lets objects be saved with an empty primary key, even though an
     # integer is expected. So we can't check for an error being raised in that
     # case for SQLite. Remove it from the suite for this next bit.
-    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3':
-        def test_required_pk(self):
-            # The primary key must be specified, so an error is raised if you
-            # try to create an object without it.
-            sid = transaction.savepoint()
-            self.assertRaises(IntegrityError,
-                Employee.objects.create, first_name="Tom", last_name="Smith"
-            )
-            transaction.savepoint_rollback(sid)
+    @skipIfDBFeature('supports_unspecified_pk')
+    def test_required_pk(self):
+        # The primary key must be specified, so an error is raised if you
+        # try to create an object without it.
+        sid = transaction.savepoint()
+        self.assertRaises(IntegrityError,
+            Employee.objects.create, first_name="Tom", last_name="Smith"
+        )
+        transaction.savepoint_rollback(sid)

+ 45 - 44
tests/modeltests/fixtures/tests.py

@@ -1,13 +1,14 @@
 import StringIO
 import sys
 
-from django.test import TestCase, TransactionTestCase
 from django.conf import settings
 from django.core import management
 from django.db import DEFAULT_DB_ALIAS
+from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
 
 from models import Article, Blog, Book, Category, Person, Spy, Tag, Visa
 
+
 class TestCaseFixtureLoadingTests(TestCase):
     fixtures = ['fixture1.json', 'fixture2.json']
 
@@ -291,46 +292,46 @@ class FixtureLoadingTests(TestCase):
         self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
 <django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Time to reform copyright</field><field type="DateTimeField" name="pub_date">2006-06-16 13:00:00</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker has no place on ESPN</field><field type="DateTimeField" name="pub_date">2006-06-16 12:00:00</field></object><object pk="1" model="fixtures.article"><field type="CharField" name="headline">Python program becomes self aware</field><field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">law</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Prince</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object></django-objects>""", format='xml', natural_keys=True)
 
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql':
-    class FixtureTransactionTests(TransactionTestCase):
-        def _dumpdata_assert(self, args, output, format='json'):
-            new_io = StringIO.StringIO()
-            management.call_command('dumpdata', *args, **{'format':format, 'stdout':new_io})
-            command_output = new_io.getvalue().strip()
-            self.assertEqual(command_output, output)
-
-        def test_format_discovery(self):
-            # Load fixture 1 again, using format discovery
-            management.call_command('loaddata', 'fixture1', verbosity=0, commit=False)
-            self.assertQuerysetEqual(Article.objects.all(), [
-                '<Article: Time to reform copyright>',
-                '<Article: Poker has no place on ESPN>',
-                '<Article: Python program becomes self aware>'
-            ])
-
-            # Try to load fixture 2 using format discovery; this will fail
-            # because there are two fixture2's in the fixtures directory
-            new_io = StringIO.StringIO()
-            management.call_command('loaddata', 'fixture2', verbosity=0, stderr=new_io)
-            output = new_io.getvalue().strip().split('\n')
-            self.assertEqual(len(output), 1)
-            self.assertTrue(output[0].startswith("Multiple fixtures named 'fixture2'"))
-
-            # object list is unaffected
-            self.assertQuerysetEqual(Article.objects.all(), [
-                '<Article: Time to reform copyright>',
-                '<Article: Poker has no place on ESPN>',
-                '<Article: Python program becomes self aware>'
-            ])
-
-            # Dump the current contents of the database as a JSON fixture
-            self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
-
-            # Load fixture 4 (compressed), using format discovery
-            management.call_command('loaddata', 'fixture4', verbosity=0, commit=False)
-            self.assertQuerysetEqual(Article.objects.all(), [
-                '<Article: Django pets kitten>',
-                '<Article: Time to reform copyright>',
-                '<Article: Poker has no place on ESPN>',
-                '<Article: Python program becomes self aware>'
-            ])
+class FixtureTransactionTests(TransactionTestCase):
+    def _dumpdata_assert(self, args, output, format='json'):
+        new_io = StringIO.StringIO()
+        management.call_command('dumpdata', *args, **{'format':format, 'stdout':new_io})
+        command_output = new_io.getvalue().strip()
+        self.assertEqual(command_output, output)
+
+    @skipUnlessDBFeature('supports_forward_references')
+    def test_format_discovery(self):
+        # Load fixture 1 again, using format discovery
+        management.call_command('loaddata', 'fixture1', verbosity=0, commit=False)
+        self.assertQuerysetEqual(Article.objects.all(), [
+            '<Article: Time to reform copyright>',
+            '<Article: Poker has no place on ESPN>',
+            '<Article: Python program becomes self aware>'
+        ])
+
+        # Try to load fixture 2 using format discovery; this will fail
+        # because there are two fixture2's in the fixtures directory
+        new_io = StringIO.StringIO()
+        management.call_command('loaddata', 'fixture2', verbosity=0, stderr=new_io)
+        output = new_io.getvalue().strip().split('\n')
+        self.assertEqual(len(output), 1)
+        self.assertTrue(output[0].startswith("Multiple fixtures named 'fixture2'"))
+
+        # object list is unaffected
+        self.assertQuerysetEqual(Article.objects.all(), [
+            '<Article: Time to reform copyright>',
+            '<Article: Poker has no place on ESPN>',
+            '<Article: Python program becomes self aware>'
+        ])
+
+        # Dump the current contents of the database as a JSON fixture
+        self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]')
+
+        # Load fixture 4 (compressed), using format discovery
+        management.call_command('loaddata', 'fixture4', verbosity=0, commit=False)
+        self.assertQuerysetEqual(Article.objects.all(), [
+            '<Article: Django pets kitten>',
+            '<Article: Time to reform copyright>',
+            '<Article: Poker has no place on ESPN>',
+            '<Article: Python program becomes self aware>'
+        ])

+ 9 - 8
tests/modeltests/lookup/models.py

@@ -4,7 +4,7 @@
 This demonstrates features of the database API.
 """
 
-from django.db import models, DEFAULT_DB_ALIAS
+from django.db import models, DEFAULT_DB_ALIAS, connection
 from django.conf import settings
 
 class Article(models.Model):
@@ -41,15 +41,16 @@ False
 # There should be some now!
 >>> Article.objects.exists()
 True
-"""}
 
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in (
-        'django.db.backends.postgresql',
-        'django.db.backends.postgresql_pysycopg2'):
-    __test__['API_TESTS'] += r"""
-# text matching tests for PostgreSQL 8.3
+# Integer value can be queried using string
 >>> Article.objects.filter(id__iexact='1')
 [<Article: Article 1>]
+
+"""}
+
+if connection.features.supports_date_lookup_using_string:
+    __test__['API_TESTS'] += r"""
+# A date lookup can be performed using a string search
 >>> Article.objects.filter(pub_date__startswith='2005')
 [<Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
 """
@@ -409,7 +410,7 @@ FieldError: Join on field 'headline' not permitted. Did you misspell 'starts' fo
 """
 
 
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql':
+if connection.features.supports_regex_backreferencing:
     __test__['API_TESTS'] += r"""
 # grouping and backreferences
 >>> Article.objects.filter(headline__regex=r'b(.).*b\1')

+ 1 - 1
tests/modeltests/test_client/tests.py

@@ -1,6 +1,6 @@
 # Validate that you can override the default test suite
 
-import unittest
+from django.utils import unittest
 
 def suite():
     """

+ 152 - 148
tests/modeltests/transactions/tests.py

@@ -1,155 +1,159 @@
-from django.test import TransactionTestCase
 from django.db import connection, transaction, IntegrityError, DEFAULT_DB_ALIAS
 from django.conf import settings
+from django.test import TransactionTestCase, skipUnlessDBFeature
 
 from models import Reporter
 
-PGSQL = 'psycopg2' in settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE']
-MYSQL = 'mysql' in settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE']
 
 class TransactionTests(TransactionTestCase):
-
-    if not MYSQL:
-
-        def create_a_reporter_then_fail(self, first, last):
-            a = Reporter(first_name=first, last_name=last)
-            a.save()
-            raise Exception("I meant to do that")
-
-        def remove_a_reporter(self, first_name):
-            r = Reporter.objects.get(first_name="Alice")
-            r.delete()
-
-        def manually_managed(self):
-            r = Reporter(first_name="Dirk", last_name="Gently")
-            r.save()
-            transaction.commit()
-
-        def manually_managed_mistake(self):
-            r = Reporter(first_name="Edward", last_name="Woodward")
-            r.save()
-            # Oops, I forgot to commit/rollback!
-
-        def execute_bad_sql(self):
-            cursor = connection.cursor()
-            cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
-            transaction.set_dirty()
-
-        def test_autocommit(self):
-            """
-            The default behavior is to autocommit after each save() action.
-            """
-            self.assertRaises(Exception,
-                self.create_a_reporter_then_fail,
-                "Alice", "Smith"
-            )
-
-            # The object created before the exception still exists
-            self.assertEqual(Reporter.objects.count(), 1)
-
-        def test_autocommit_decorator(self):
-            """
-            The autocommit decorator works exactly the same as the default behavior.
-            """
-            autocomitted_create_then_fail = transaction.autocommit(
-                self.create_a_reporter_then_fail
-            )
-            self.assertRaises(Exception,
-                autocomitted_create_then_fail,
-                "Alice", "Smith"
-            )
-            # Again, the object created before the exception still exists
-            self.assertEqual(Reporter.objects.count(), 1)
-
-        def test_autocommit_decorator_with_using(self):
-            """
-            The autocommit decorator also works with a using argument.
-            """
-            autocomitted_create_then_fail = transaction.autocommit(using='default')(
-                self.create_a_reporter_then_fail
-            )
-            self.assertRaises(Exception,
-                autocomitted_create_then_fail,
-                "Alice", "Smith"
-            )
-            # Again, the object created before the exception still exists
-            self.assertEqual(Reporter.objects.count(), 1)
-
-        def test_commit_on_success(self):
-            """
-            With the commit_on_success decorator, the transaction is only committed
-            if the function doesn't throw an exception.
-            """
-            committed_on_success = transaction.commit_on_success(
-                self.create_a_reporter_then_fail)
-            self.assertRaises(Exception, committed_on_success, "Dirk", "Gently")
-            # This time the object never got saved
-            self.assertEqual(Reporter.objects.count(), 0)
-
-        def test_commit_on_success_with_using(self):
-            """
-            The commit_on_success decorator also works with a using argument.
-            """
-            using_committed_on_success = transaction.commit_on_success(using='default')(
-                self.create_a_reporter_then_fail
-            )
-            self.assertRaises(Exception,
-                using_committed_on_success,
-                "Dirk", "Gently"
-            )
-            # This time the object never got saved
-            self.assertEqual(Reporter.objects.count(), 0)
-
-        def test_commit_on_success_succeed(self):
-            """
-            If there aren't any exceptions, the data will get saved.
-            """
-            Reporter.objects.create(first_name="Alice", last_name="Smith")
-            remove_comitted_on_success = transaction.commit_on_success(
-                self.remove_a_reporter
-            )
-            remove_comitted_on_success("Alice")
-            self.assertEqual(list(Reporter.objects.all()), [])
-
-        def test_manually_managed(self):
-            """
-            You can manually manage transactions if you really want to, but you
-            have to remember to commit/rollback.
-            """
-            manually_managed = transaction.commit_manually(self.manually_managed)
-            manually_managed()
-            self.assertEqual(Reporter.objects.count(), 1)
-
-        def test_manually_managed_mistake(self):
-            """
-            If you forget, you'll get bad errors.
-            """
-            manually_managed_mistake = transaction.commit_manually(
-                self.manually_managed_mistake
-            )
-            self.assertRaises(transaction.TransactionManagementError,
-                manually_managed_mistake)
-
-        def test_manually_managed_with_using(self):
-            """
-            The commit_manually function also works with a using argument.
-            """
-            using_manually_managed_mistake = transaction.commit_manually(using='default')(
-                self.manually_managed_mistake
-            )
-            self.assertRaises(transaction.TransactionManagementError,
-                using_manually_managed_mistake
-            )
-
-    if PGSQL:
-
-        def test_bad_sql(self):
-            """
-            Regression for #11900: If a function wrapped by commit_on_success
-            writes a transaction that can't be committed, that transaction should
-            be rolled back. The bug is only visible using the psycopg2 backend,
-            though the fix is generally a good idea.
-            """
-            execute_bad_sql = transaction.commit_on_success(self.execute_bad_sql)
-            self.assertRaises(IntegrityError, execute_bad_sql)
-            transaction.rollback()
+    def create_a_reporter_then_fail(self, first, last):
+        a = Reporter(first_name=first, last_name=last)
+        a.save()
+        raise Exception("I meant to do that")
+
+    def remove_a_reporter(self, first_name):
+        r = Reporter.objects.get(first_name="Alice")
+        r.delete()
+
+    def manually_managed(self):
+        r = Reporter(first_name="Dirk", last_name="Gently")
+        r.save()
+        transaction.commit()
+
+    def manually_managed_mistake(self):
+        r = Reporter(first_name="Edward", last_name="Woodward")
+        r.save()
+        # Oops, I forgot to commit/rollback!
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_autocommit(self):
+        """
+        The default behavior is to autocommit after each save() action.
+        """
+        self.assertRaises(Exception,
+            self.create_a_reporter_then_fail,
+            "Alice", "Smith"
+        )
+
+        # The object created before the exception still exists
+        self.assertEqual(Reporter.objects.count(), 1)
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_autocommit_decorator(self):
+        """
+        The autocommit decorator works exactly the same as the default behavior.
+        """
+        autocomitted_create_then_fail = transaction.autocommit(
+            self.create_a_reporter_then_fail
+        )
+        self.assertRaises(Exception,
+            autocomitted_create_then_fail,
+            "Alice", "Smith"
+        )
+        # Again, the object created before the exception still exists
+        self.assertEqual(Reporter.objects.count(), 1)
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_autocommit_decorator_with_using(self):
+        """
+        The autocommit decorator also works with a using argument.
+        """
+        autocomitted_create_then_fail = transaction.autocommit(using='default')(
+            self.create_a_reporter_then_fail
+        )
+        self.assertRaises(Exception,
+            autocomitted_create_then_fail,
+            "Alice", "Smith"
+        )
+        # Again, the object created before the exception still exists
+        self.assertEqual(Reporter.objects.count(), 1)
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_commit_on_success(self):
+        """
+        With the commit_on_success decorator, the transaction is only committed
+        if the function doesn't throw an exception.
+        """
+        committed_on_success = transaction.commit_on_success(
+            self.create_a_reporter_then_fail)
+        self.assertRaises(Exception, committed_on_success, "Dirk", "Gently")
+        # This time the object never got saved
+        self.assertEqual(Reporter.objects.count(), 0)
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_commit_on_success_with_using(self):
+        """
+        The commit_on_success decorator also works with a using argument.
+        """
+        using_committed_on_success = transaction.commit_on_success(using='default')(
+            self.create_a_reporter_then_fail
+        )
+        self.assertRaises(Exception,
+            using_committed_on_success,
+            "Dirk", "Gently"
+        )
+        # This time the object never got saved
+        self.assertEqual(Reporter.objects.count(), 0)
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_commit_on_success_succeed(self):
+        """
+        If there aren't any exceptions, the data will get saved.
+        """
+        Reporter.objects.create(first_name="Alice", last_name="Smith")
+        remove_comitted_on_success = transaction.commit_on_success(
+            self.remove_a_reporter
+        )
+        remove_comitted_on_success("Alice")
+        self.assertEqual(list(Reporter.objects.all()), [])
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_manually_managed(self):
+        """
+        You can manually manage transactions if you really want to, but you
+        have to remember to commit/rollback.
+        """
+        manually_managed = transaction.commit_manually(self.manually_managed)
+        manually_managed()
+        self.assertEqual(Reporter.objects.count(), 1)
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_manually_managed_mistake(self):
+        """
+        If you forget, you'll get bad errors.
+        """
+        manually_managed_mistake = transaction.commit_manually(
+            self.manually_managed_mistake
+        )
+        self.assertRaises(transaction.TransactionManagementError,
+            manually_managed_mistake)
+
+    @skipUnlessDBFeature('supports_transactions')
+    def test_manually_managed_with_using(self):
+        """
+        The commit_manually function also works with a using argument.
+        """
+        using_manually_managed_mistake = transaction.commit_manually(using='default')(
+            self.manually_managed_mistake
+        )
+        self.assertRaises(transaction.TransactionManagementError,
+            using_manually_managed_mistake
+        )
+
+class TransactionRollbackTests(TransactionTestCase):
+    def execute_bad_sql(self):
+        cursor = connection.cursor()
+        cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
+        transaction.set_dirty()
+
+    @skipUnlessDBFeature('requires_rollback_on_dirty_transaction')
+    def test_bad_sql(self):
+        """
+        Regression for #11900: If a function wrapped by commit_on_success
+        writes a transaction that can't be committed, that transaction should
+        be rolled back. The bug is only visible using the psycopg2 backend,
+        though the fix is generally a good idea.
+        """
+        execute_bad_sql = transaction.commit_on_success(self.execute_bad_sql)
+        self.assertRaises(IntegrityError, execute_bad_sql)
+        transaction.rollback()

+ 1 - 1
tests/modeltests/validation/__init__.py

@@ -1,4 +1,4 @@
-import unittest
+from django.utils import unittest
 
 from django.core.exceptions import ValidationError
 

+ 3 - 1
tests/modeltests/validation/test_unique.py

@@ -1,7 +1,9 @@
-import unittest
 import datetime
+
 from django.conf import settings
 from django.db import connection
+from django.utils import unittest
+
 from models import CustomPKModel, UniqueTogetherModel, UniqueFieldsModel, UniqueForDateModel, ModelToValidate
 
 

+ 2 - 1
tests/modeltests/validation/validators.py

@@ -1,4 +1,5 @@
-from unittest import TestCase
+from django.utils.unittest import TestCase
+
 from modeltests.validation import ValidationTestCase
 from models import *
 

+ 3 - 1
tests/modeltests/validators/tests.py

@@ -1,10 +1,12 @@
 # -*- coding: utf-8 -*-
 import re
 import types
-from unittest import TestCase
 from datetime import datetime, timedelta
+
 from django.core.exceptions import ValidationError
 from django.core.validators import *
+from django.utils.unittest import TestCase
+
 
 NOW = datetime.now()
 

+ 1 - 1
tests/regressiontests/admin_scripts/tests.py

@@ -4,13 +4,13 @@ advertised - especially with regards to the handling of the DJANGO_SETTINGS_MODU
 and default settings.py files.
 """
 import os
-import unittest
 import shutil
 import sys
 import re
 
 from django import conf, bin, get_version
 from django.conf import settings
+from django.utils import unittest
 
 class AdminScriptTestCase(unittest.TestCase):
     def write_settings(self, filename, apps=None, is_dir=False, sdict=None):

+ 5 - 6
tests/regressiontests/admin_util/tests.py

@@ -1,16 +1,15 @@
 from datetime import datetime
-import unittest
 
 from django.conf import settings
-from django.db import models
-from django.utils.formats import localize
-from django.test import TestCase
-
 from django.contrib import admin
 from django.contrib.admin.util import display_for_field, label_for_field, lookup_field
+from django.contrib.admin.util import NestedObjects
 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
 from django.contrib.sites.models import Site
-from django.contrib.admin.util import NestedObjects
+from django.db import models
+from django.test import TestCase
+from django.utils import unittest
+from django.utils.formats import localize
 
 from models import Article, Count
 

+ 34 - 32
tests/regressiontests/admin_views/tests.py

@@ -12,13 +12,14 @@ from django.contrib.admin.sites import LOGIN_FORM_KEY
 from django.contrib.admin.util import quote
 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
 from django.forms.util import ErrorList
+import django.template.context
 from django.test import TestCase
 from django.utils import formats
 from django.utils.cache import get_max_age
 from django.utils.encoding import iri_to_uri
 from django.utils.html import escape
 from django.utils.translation import get_date_formats, activate, deactivate
-import django.template.context
+from django.utils import unittest
 
 # local test models
 from models import Article, BarAccount, CustomArticle, EmptyModel, \
@@ -2210,51 +2211,52 @@ class UserAdminTest(TestCase):
         self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD)
 
 try:
-    # If docutils isn't installed, skip the AdminDocs tests.
     import docutils
+except ImportError:
+    docutils = None
 
-    class AdminDocsTest(TestCase):
-        fixtures = ['admin-views-users.xml']
+#@unittest.skipUnless(docutils, "no docutils installed.")
+class AdminDocsTest(TestCase):
+    fixtures = ['admin-views-users.xml']
 
-        def setUp(self):
-            self.client.login(username='super', password='secret')
+    def setUp(self):
+        self.client.login(username='super', password='secret')
 
-        def tearDown(self):
-            self.client.logout()
+    def tearDown(self):
+        self.client.logout()
 
-        def test_tags(self):
-            response = self.client.get('/test_admin/admin/doc/tags/')
+    def test_tags(self):
+        response = self.client.get('/test_admin/admin/doc/tags/')
 
-            # The builtin tag group exists
-            self.assertContains(response, "<h2>Built-in tags</h2>", count=2)
+        # The builtin tag group exists
+        self.assertContains(response, "<h2>Built-in tags</h2>", count=2)
 
-            # A builtin tag exists in both the index and detail
-            self.assertContains(response, '<h3 id="built_in-autoescape">autoescape</h3>')
-            self.assertContains(response, '<li><a href="#built_in-autoescape">autoescape</a></li>')
+        # A builtin tag exists in both the index and detail
+        self.assertContains(response, '<h3 id="built_in-autoescape">autoescape</h3>')
+        self.assertContains(response, '<li><a href="#built_in-autoescape">autoescape</a></li>')
 
-            # An app tag exists in both the index and detail
-            self.assertContains(response, '<h3 id="flatpages-get_flatpages">get_flatpages</h3>')
-            self.assertContains(response, '<li><a href="#flatpages-get_flatpages">get_flatpages</a></li>')
+        # An app tag exists in both the index and detail
+        self.assertContains(response, '<h3 id="flatpages-get_flatpages">get_flatpages</h3>')
+        self.assertContains(response, '<li><a href="#flatpages-get_flatpages">get_flatpages</a></li>')
 
-            # The admin list tag group exists
-            self.assertContains(response, "<h2>admin_list</h2>", count=2)
+        # The admin list tag group exists
+        self.assertContains(response, "<h2>admin_list</h2>", count=2)
 
-            # An admin list tag exists in both the index and detail
-            self.assertContains(response, '<h3 id="admin_list-admin_actions">admin_actions</h3>')
-            self.assertContains(response, '<li><a href="#admin_list-admin_actions">admin_actions</a></li>')
+        # An admin list tag exists in both the index and detail
+        self.assertContains(response, '<h3 id="admin_list-admin_actions">admin_actions</h3>')
+        self.assertContains(response, '<li><a href="#admin_list-admin_actions">admin_actions</a></li>')
 
-        def test_filters(self):
-            response = self.client.get('/test_admin/admin/doc/filters/')
+    def test_filters(self):
+        response = self.client.get('/test_admin/admin/doc/filters/')
 
-            # The builtin filter group exists
-            self.assertContains(response, "<h2>Built-in filters</h2>", count=2)
+        # The builtin filter group exists
+        self.assertContains(response, "<h2>Built-in filters</h2>", count=2)
 
-            # A builtin filter exists in both the index and detail
-            self.assertContains(response, '<h3 id="built_in-add">add</h3>')
-            self.assertContains(response, '<li><a href="#built_in-add">add</a></li>')
+        # A builtin filter exists in both the index and detail
+        self.assertContains(response, '<h3 id="built_in-add">add</h3>')
+        self.assertContains(response, '<li><a href="#built_in-add">add</a></li>')
 
-except ImportError:
-    pass
+AdminDocsTest = unittest.skipUnless(docutils, "no docutils installed.")(AdminDocsTest)
 
 class ValidXHTMLTests(TestCase):
     fixtures = ['admin-views-users.xml']

+ 3 - 2
tests/regressiontests/admin_widgets/tests.py

@@ -3,9 +3,10 @@
 from django import forms
 from django.contrib import admin
 from django.contrib.admin import widgets
-from unittest import TestCase
-from django.test import TestCase as DjangoTestCase
 from django.db.models import DateField
+from django.test import TestCase as DjangoTestCase
+from django.utils.unittest import TestCase
+
 import models
 
 class AdminFormfieldForDBFieldTests(TestCase):

+ 83 - 102
tests/regressiontests/aggregation_regress/tests.py

@@ -3,32 +3,13 @@ from decimal import Decimal
 
 from django.core.exceptions import FieldError
 from django.conf import settings
-from django.test import TestCase, Approximate
+from django.test import TestCase, Approximate, skipUnlessDBFeature
 from django.db import DEFAULT_DB_ALIAS
 from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F
 
 from regressiontests.aggregation_regress.models import *
 
 
-def run_stddev_tests():
-    """Check to see if StdDev/Variance tests should be run.
-
-    Stddev and Variance are not guaranteed to be available for SQLite, and
-    are not available for PostgreSQL before 8.2.
-    """
-    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.sqlite3':
-        return False
-
-    class StdDevPop(object):
-        sql_function = 'STDDEV_POP'
-
-    try:
-        connection.ops.check_aggregate_support(StdDevPop())
-    except:
-        return False
-    return True
-
-
 class AggregationTests(TestCase):
     def assertObjectAttrs(self, obj, **kwargs):
         for attr, value in kwargs.iteritems():
@@ -75,27 +56,27 @@ class AggregationTests(TestCase):
         qs2 = books.filter(id__in=list(qs))
         self.assertEqual(list(qs1), list(qs2))
 
-    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle':
-        def test_annotate_with_extra(self):
-            """
-            Regression test for #11916: Extra params + aggregation creates
-            incorrect SQL.
-            """
-            #oracle doesn't support subqueries in group by clause
-            shortest_book_sql = """
-            SELECT name
-            FROM aggregation_regress_book b
-            WHERE b.publisher_id = aggregation_regress_publisher.id
-            ORDER BY b.pages
-            LIMIT 1
-            """
-            # tests that this query does not raise a DatabaseError due to the full
-            # subselect being (erroneously) added to the GROUP BY parameters
-            qs = Publisher.objects.extra(select={
-                'name_of_shortest_book': shortest_book_sql,
-            }).annotate(total_books=Count('book'))
-            # force execution of the query
-            list(qs)
+    @skipUnlessDBFeature('supports_subqueries_in_group_by')
+    def test_annotate_with_extra(self):
+        """
+        Regression test for #11916: Extra params + aggregation creates
+        incorrect SQL.
+        """
+        #oracle doesn't support subqueries in group by clause
+        shortest_book_sql = """
+        SELECT name
+        FROM aggregation_regress_book b
+        WHERE b.publisher_id = aggregation_regress_publisher.id
+        ORDER BY b.pages
+        LIMIT 1
+        """
+        # tests that this query does not raise a DatabaseError due to the full
+        # subselect being (erroneously) added to the GROUP BY parameters
+        qs = Publisher.objects.extra(select={
+            'name_of_shortest_book': shortest_book_sql,
+        }).annotate(total_books=Count('book'))
+        # force execution of the query
+        list(qs)
 
     def test_aggregate(self):
         # Ordering requests are ignored
@@ -641,64 +622,64 @@ class AggregationTests(TestCase):
             lambda: Book.objects.annotate(mean_age=Avg('authors__age')).annotate(Avg('mean_age'))
         )
 
-    if run_stddev_tests():
-        def test_stddev(self):
-            self.assertEqual(
-                Book.objects.aggregate(StdDev('pages')),
-                {'pages__stddev': Approximate(311.46, 1)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(StdDev('rating')),
-                {'rating__stddev': Approximate(0.60, 1)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(StdDev('price')),
-                {'price__stddev': Approximate(24.16, 2)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(StdDev('pages', sample=True)),
-                {'pages__stddev': Approximate(341.19, 2)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(StdDev('rating', sample=True)),
-                {'rating__stddev': Approximate(0.66, 2)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(StdDev('price', sample=True)),
-                {'price__stddev': Approximate(26.46, 1)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(Variance('pages')),
-                {'pages__variance': Approximate(97010.80, 1)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(Variance('rating')),
-                {'rating__variance': Approximate(0.36, 1)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(Variance('price')),
-                {'price__variance': Approximate(583.77, 1)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(Variance('pages', sample=True)),
-                {'pages__variance': Approximate(116412.96, 1)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(Variance('rating', sample=True)),
-                {'rating__variance': Approximate(0.44, 2)}
-            )
-
-            self.assertEqual(
-                Book.objects.aggregate(Variance('price', sample=True)),
-                {'price__variance': Approximate(700.53, 2)}
-            )
+    @skipUnlessDBFeature('supports_stddev')
+    def test_stddev(self):
+        self.assertEqual(
+            Book.objects.aggregate(StdDev('pages')),
+            {'pages__stddev': Approximate(311.46, 1)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(StdDev('rating')),
+            {'rating__stddev': Approximate(0.60, 1)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(StdDev('price')),
+            {'price__stddev': Approximate(24.16, 2)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(StdDev('pages', sample=True)),
+            {'pages__stddev': Approximate(341.19, 2)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(StdDev('rating', sample=True)),
+            {'rating__stddev': Approximate(0.66, 2)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(StdDev('price', sample=True)),
+            {'price__stddev': Approximate(26.46, 1)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(Variance('pages')),
+            {'pages__variance': Approximate(97010.80, 1)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(Variance('rating')),
+            {'rating__variance': Approximate(0.36, 1)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(Variance('price')),
+            {'price__variance': Approximate(583.77, 1)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(Variance('pages', sample=True)),
+            {'pages__variance': Approximate(116412.96, 1)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(Variance('rating', sample=True)),
+            {'rating__variance': Approximate(0.44, 2)}
+        )
+
+        self.assertEqual(
+            Book.objects.aggregate(Variance('price', sample=True)),
+            {'price__variance': Approximate(700.53, 2)}
+        )

+ 1 - 1
tests/regressiontests/app_loading/tests.py

@@ -2,10 +2,10 @@ import copy
 import os
 import sys
 import time
-from unittest import TestCase
 
 from django.conf import Settings
 from django.db.models.loading import cache, load_app
+from django.utils.unittest import TestCase
 
 __test__ = {"API_TESTS": """
 Test the globbing of INSTALLED_APPS.

+ 1 - 1
tests/regressiontests/backends/models.py

@@ -28,7 +28,7 @@ class SchoolClass(models.Model):
 
 # Unfortunately, the following model breaks MySQL hard.
 # Until #13711 is fixed, this test can't be run under MySQL.
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql':
+if connection.features.supports_long_model_names:
     class VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ(models.Model):
         class Meta:
             # We need to use a short actual table name or

+ 83 - 83
tests/regressiontests/backends/tests.py

@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 # Unit and doctests for specific database backends.
 import datetime
-import unittest
 
 from django.conf import settings
 from django.core import management
@@ -9,48 +8,46 @@ from django.core.management.color import no_style
 from django.db import backend, connection, connections, DEFAULT_DB_ALIAS
 from django.db.backends.signals import connection_created
 from django.db.backends.postgresql import version as pg_version
-from django.test import TestCase
+from django.test import TestCase, skipUnlessDBFeature
+from django.utils import unittest
 
 from regressiontests.backends import models
 
-class Callproc(unittest.TestCase):
+class OracleChecks(unittest.TestCase):
 
+    @unittest.skipUnless(connection.vendor == 'oracle',
+                         "No need to check Oracle cursor semantics")
     def test_dbms_session(self):
         # If the backend is Oracle, test that we can call a standard
         # stored procedure through our cursor wrapper.
-        if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
-            convert_unicode = backend.convert_unicode
-            cursor = connection.cursor()
-            cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'),
-                            [convert_unicode('_django_testing!'),])
-            return True
-        else:
-            return True
+        convert_unicode = backend.convert_unicode
+        cursor = connection.cursor()
+        cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'),
+                        [convert_unicode('_django_testing!'),])
 
+    @unittest.skipUnless(connection.vendor == 'oracle',
+                         "No need to check Oracle cursor semantics")
     def test_cursor_var(self):
         # If the backend is Oracle, test that we can pass cursor variables
         # as query parameters.
-        if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
-            cursor = connection.cursor()
-            var = cursor.var(backend.Database.STRING)
-            cursor.execute("BEGIN %s := 'X'; END; ", [var])
-            self.assertEqual(var.getvalue(), 'X')
-
-
-class LongString(unittest.TestCase):
+        cursor = connection.cursor()
+        var = cursor.var(backend.Database.STRING)
+        cursor.execute("BEGIN %s := 'X'; END; ", [var])
+        self.assertEqual(var.getvalue(), 'X')
 
+    @unittest.skipUnless(connection.vendor == 'oracle',
+                         "No need to check Oracle cursor semantics")
     def test_long_string(self):
         # If the backend is Oracle, test that we can save a text longer
         # than 4000 chars and read it properly
-        if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
-            c = connection.cursor()
-            c.execute('CREATE TABLE ltext ("TEXT" NCLOB)')
-            long_str = ''.join([unicode(x) for x in xrange(4000)])
-            c.execute('INSERT INTO ltext VALUES (%s)',[long_str])
-            c.execute('SELECT text FROM ltext')
-            row = c.fetchone()
-            self.assertEquals(long_str, row[0].read())
-            c.execute('DROP TABLE ltext')
+        c = connection.cursor()
+        c.execute('CREATE TABLE ltext ("TEXT" NCLOB)')
+        long_str = ''.join([unicode(x) for x in xrange(4000)])
+        c.execute('INSERT INTO ltext VALUES (%s)',[long_str])
+        c.execute('SELECT text FROM ltext')
+        row = c.fetchone()
+        self.assertEqual(long_str, row[0].read())
+        c.execute('DROP TABLE ltext')
 
 class DateQuotingTest(TestCase):
 
@@ -97,46 +94,48 @@ class ParameterHandlingTest(TestCase):
 # Unfortunately, the following tests would be a good test to run on all
 # backends, but it breaks MySQL hard. Until #13711 is fixed, it can't be run
 # everywhere (although it would be an effective test of #13711).
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql':
-    class LongNameTest(TestCase):
-        """Long primary keys and model names can result in a sequence name
-        that exceeds the database limits, which will result in truncation
-        on certain databases (e.g., Postgres). The backend needs to use
-        the correct sequence name in last_insert_id and other places, so
-        check it is. Refs #8901.
-        """
-
-        def test_sequence_name_length_limits_create(self):
-            """Test creation of model with long name and long pk name doesn't error. Ref #8901"""
-            models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ.objects.create()
-
-        def test_sequence_name_length_limits_m2m(self):
-            """Test an m2m save of a model with a long name and a long m2m field name doesn't error as on Django >=1.2 this now uses object saves. Ref #8901"""
-            obj = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ.objects.create()
-            rel_obj = models.Person.objects.create(first_name='Django', last_name='Reinhardt')
-            obj.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.add(rel_obj)
-
-        def test_sequence_name_length_limits_flush(self):
-            """Test that sequence resetting as part of a flush with model with long name and long pk name doesn't error. Ref #8901"""
-            # A full flush is expensive to the full test, so we dig into the
-            # internals to generate the likely offending SQL and run it manually
-
-            # Some convenience aliases
-            VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
-            VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through
-            tables = [
-                VLM._meta.db_table,
-                VLM_m2m._meta.db_table,
-            ]
-            sequences = [
-                {
-                    'column': VLM._meta.pk.column,
-                    'table': VLM._meta.db_table
-                },
-            ]
-            cursor = connection.cursor()
-            for statement in connection.ops.sql_flush(no_style(), tables, sequences):
-                cursor.execute(statement)
+class LongNameTest(TestCase):
+    """Long primary keys and model names can result in a sequence name
+    that exceeds the database limits, which will result in truncation
+    on certain databases (e.g., Postgres). The backend needs to use
+    the correct sequence name in last_insert_id and other places, so
+    check it is. Refs #8901.
+    """
+
+    @skipUnlessDBFeature('supports_long_model_names')
+    def test_sequence_name_length_limits_create(self):
+        """Test creation of model with long name and long pk name doesn't error. Ref #8901"""
+        models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ.objects.create()
+
+    @skipUnlessDBFeature('supports_long_model_names')
+    def test_sequence_name_length_limits_m2m(self):
+        """Test an m2m save of a model with a long name and a long m2m field name doesn't error as on Django >=1.2 this now uses object saves. Ref #8901"""
+        obj = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ.objects.create()
+        rel_obj = models.Person.objects.create(first_name='Django', last_name='Reinhardt')
+        obj.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.add(rel_obj)
+
+    @skipUnlessDBFeature('supports_long_model_names')
+    def test_sequence_name_length_limits_flush(self):
+        """Test that sequence resetting as part of a flush with model with long name and long pk name doesn't error. Ref #8901"""
+        # A full flush is expensive to the full test, so we dig into the
+        # internals to generate the likely offending SQL and run it manually
+
+        # Some convenience aliases
+        VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+        VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through
+        tables = [
+            VLM._meta.db_table,
+            VLM_m2m._meta.db_table,
+        ]
+        sequences = [
+            {
+                'column': VLM._meta.pk.column,
+                'table': VLM._meta.db_table
+            },
+        ]
+        cursor = connection.cursor()
+        for statement in connection.ops.sql_flush(no_style(), tables, sequences):
+            cursor.execute(statement)
 
 class SequenceResetTest(TestCase):
     def test_generic_relation(self):
@@ -170,22 +169,22 @@ class PostgresVersionTest(TestCase):
 # Unfortunately with sqlite3 the in-memory test database cannot be
 # closed, and so it cannot be re-opened during testing, and so we
 # sadly disable this test for now.
-if settings.DATABASES[DEFAULT_DB_ALIAS]["ENGINE"] != "django.db.backends.sqlite3":
-    class ConnectionCreatedSignalTest(TestCase):
-        def test_signal(self):
-            data = {}
-            def receiver(sender, connection, **kwargs):
-                data["connection"] = connection
-
-            connection_created.connect(receiver)
-            connection.close()
-            cursor = connection.cursor()
-            self.assertTrue(data["connection"] is connection)
+class ConnectionCreatedSignalTest(TestCase):
+    @skipUnlessDBFeature('test_db_allows_multiple_connections')
+    def test_signal(self):
+        data = {}
+        def receiver(sender, connection, **kwargs):
+            data["connection"] = connection
+
+        connection_created.connect(receiver)
+        connection.close()
+        cursor = connection.cursor()
+        self.assertTrue(data["connection"] is connection)
 
-            connection_created.disconnect(receiver)
-            data.clear()
-            cursor = connection.cursor()
-            self.assertTrue(data == {})
+        connection_created.disconnect(receiver)
+        data.clear()
+        cursor = connection.cursor()
+        self.assertTrue(data == {})
 
 
 class BackendTestCase(TestCase):
@@ -225,3 +224,4 @@ class BackendTestCase(TestCase):
         self.assertEqual(cursor.fetchone(), (u'Clark', u'Kent'))
         self.assertEqual(list(cursor.fetchmany(2)), [(u'Jane', u'Doe'), (u'John', u'Doe')])
         self.assertEqual(list(cursor.fetchall()), [(u'Mary', u'Agnelline'), (u'Peter', u'Parker')])
+

+ 1 - 1
tests/regressiontests/bash_completion/tests.py

@@ -2,12 +2,12 @@
 A series of tests to establish that the command-line bash completion works.
 """
 import os
-import unittest
 import sys
 import StringIO
 
 from django.conf import settings
 from django.core.management import ManagementUtility
+from django.utils import unittest
 
 class BashCompletionTests(unittest.TestCase):
     """

+ 2 - 1
tests/regressiontests/bug639/tests.py

@@ -6,9 +6,10 @@ ModelForm's save() method causes Model.save() to be called more than once.
 
 import os
 import shutil
-import unittest
 
 from django.core.files.uploadedfile import SimpleUploadedFile
+from django.utils import unittest
+
 from regressiontests.bug639.models import Photo, PhotoForm, temp_storage_dir
 
 class Bug639Test(unittest.TestCase):

+ 1 - 2
tests/regressiontests/bug8245/tests.py

@@ -1,6 +1,5 @@
-from unittest import TestCase
-
 from django.contrib import admin
+from django.utils.unittest import TestCase
 
 
 class Bug8245Test(TestCase):

+ 2 - 1
tests/regressiontests/builtin_server/tests.py

@@ -1,6 +1,7 @@
-from unittest import TestCase
 from StringIO import StringIO
+
 from django.core.servers.basehttp import ServerHandler
+from django.utils.unittest import TestCase
 
 #
 # Tests for #9659: wsgi.file_wrapper in the builtin server.

+ 2 - 1
tests/regressiontests/cache/tests.py

@@ -7,7 +7,6 @@ import os
 import shutil
 import tempfile
 import time
-import unittest
 import warnings
 
 from django.conf import settings
@@ -17,6 +16,7 @@ from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWa
 from django.http import HttpResponse, HttpRequest
 from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware
 from django.utils import translation
+from django.utils import unittest
 from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key
 from django.utils.hashcompat import md5_constructor
 from regressiontests.cache.models import Poll, expensive_calculation
@@ -393,6 +393,7 @@ class BaseCacheTests(object):
         # runner doesn't add any global warning filters (it currently
         # does not).
         warnings.resetwarnings()
+        warnings.simplefilter("ignore", PendingDeprecationWarning)
 
 class DBCacheTests(unittest.TestCase, BaseCacheTests):
     def setUp(self):

+ 9 - 8
tests/regressiontests/datatypes/tests.py

@@ -1,10 +1,11 @@
 import datetime
+
+from django.conf import settings
 from django.db import DEFAULT_DB_ALIAS
-from django.test import TestCase
+from django.test import TestCase, skipIfDBFeature
 from django.utils import tzinfo
 
 from models import Donut, RumBaba
-from django.conf import settings
 
 class DataTypesTestCase(TestCase):
 
@@ -73,14 +74,14 @@ class DataTypesTestCase(TestCase):
         newd = Donut.objects.get(id=d.id)
         self.assert_(isinstance(newd.review, unicode))
 
-    def test_tz_awareness_mysql(self):
+    @skipIfDBFeature('supports_timezones')
+    def test_error_on_timezone(self):
         """Regression test for #8354: the MySQL backend should raise an error
         if given a timezone-aware datetime object."""
-        if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql':
-            dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0))
-            d = Donut(name='Bear claw', consumed_at=dt)
-            self.assertRaises(ValueError, d.save)
-            # ValueError: MySQL backend does not support timezone-aware datetimes.
+        dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0))
+        d = Donut(name='Bear claw', consumed_at=dt)
+        self.assertRaises(ValueError, d.save)
+        # ValueError: MySQL backend does not support timezone-aware datetimes.
 
     def test_datefield_auto_now_add(self):
         """Regression test for #10970, auto_now_add for DateField should store

+ 10 - 9
tests/regressiontests/decorators/tests.py

@@ -1,18 +1,19 @@
-from unittest import TestCase
 from sys import version_info
 try:
     from functools import wraps
 except ImportError:
     from django.utils.functional import wraps  # Python 2.4 fallback.
 
+from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
+from django.contrib.admin.views.decorators import staff_member_required
 from django.http import HttpResponse, HttpRequest
+from django.utils.decorators import method_decorator
 from django.utils.functional import allow_lazy, lazy, memoize
+from django.utils.unittest import TestCase
 from django.views.decorators.http import require_http_methods, require_GET, require_POST
 from django.views.decorators.vary import vary_on_headers, vary_on_cookie
 from django.views.decorators.cache import cache_page, never_cache, cache_control
-from django.utils.decorators import method_decorator
-from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
-from django.contrib.admin.views.decorators import staff_member_required
+
 
 def fully_decorated(request):
     """Expected __doc__"""
@@ -70,25 +71,25 @@ class DecoratorsTest(TestCase):
         def test1(user):
             user.decorators_applied.append('test1')
             return True
-            
+
         def test2(user):
             user.decorators_applied.append('test2')
             return True
-            
+
         def callback(request):
             return request.user.decorators_applied
 
         callback = user_passes_test(test1)(callback)
         callback = user_passes_test(test2)(callback)
-        
+
         class DummyUser(object): pass
         class DummyRequest(object): pass
-        
+
         request = DummyRequest()
         request.user = DummyUser()
         request.user.decorators_applied = []
         response = callback(request)
-        
+
         self.assertEqual(response, ['test2', 'test1'])
 
     def test_cache_page_new_style(self):

+ 54 - 53
tests/regressiontests/delete_regress/tests.py

@@ -2,64 +2,65 @@ import datetime
 
 from django.conf import settings
 from django.db import backend, connection, transaction, DEFAULT_DB_ALIAS
-from django.test import TestCase, TransactionTestCase
+from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
 
 from models import Book, Award, AwardNote, Person, Child, Toy, PlayedWith, PlayedWithNote
 
+
 # Can't run this test under SQLite, because you can't
 # get two connections to an in-memory database.
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3':
-    class DeleteLockingTest(TransactionTestCase):
-        def setUp(self):
-            # Create a second connection to the default database
-            conn_settings = settings.DATABASES[DEFAULT_DB_ALIAS]
-            self.conn2 = backend.DatabaseWrapper({
-                'HOST': conn_settings['HOST'],
-                'NAME': conn_settings['NAME'],
-                'OPTIONS': conn_settings['OPTIONS'],
-                'PASSWORD': conn_settings['PASSWORD'],
-                'PORT': conn_settings['PORT'],
-                'USER': conn_settings['USER'],
-                'TIME_ZONE': settings.TIME_ZONE,
-            })
-
-            # Put both DB connections into managed transaction mode
-            transaction.enter_transaction_management()
-            transaction.managed(True)
-            self.conn2._enter_transaction_management(True)
-
-        def tearDown(self):
-            # Close down the second connection.
-            transaction.leave_transaction_management()
-            self.conn2.close()
-
-        def test_concurrent_delete(self):
-            "Deletes on concurrent transactions don't collide and lock the database. Regression for #9479"
-
-            # Create some dummy data
-            b1 = Book(id=1, pagecount=100)
-            b2 = Book(id=2, pagecount=200)
-            b3 = Book(id=3, pagecount=300)
-            b1.save()
-            b2.save()
-            b3.save()
-
-            transaction.commit()
-
-            self.assertEquals(3, Book.objects.count())
-
-            # Delete something using connection 2.
-            cursor2 = self.conn2.cursor()
-            cursor2.execute('DELETE from delete_regress_book WHERE id=1')
-            self.conn2._commit();
-
-            # Now perform a queryset delete that covers the object
-            # deleted in connection 2. This causes an infinite loop
-            # under MySQL InnoDB unless we keep track of already
-            # deleted objects.
-            Book.objects.filter(pagecount__lt=250).delete()
-            transaction.commit()
-            self.assertEquals(1, Book.objects.count())
+class DeleteLockingTest(TransactionTestCase):
+    def setUp(self):
+        # Create a second connection to the default database
+        conn_settings = settings.DATABASES[DEFAULT_DB_ALIAS]
+        self.conn2 = backend.DatabaseWrapper({
+            'HOST': conn_settings['HOST'],
+            'NAME': conn_settings['NAME'],
+            'OPTIONS': conn_settings['OPTIONS'],
+            'PASSWORD': conn_settings['PASSWORD'],
+            'PORT': conn_settings['PORT'],
+            'USER': conn_settings['USER'],
+            'TIME_ZONE': settings.TIME_ZONE,
+        })
+
+        # Put both DB connections into managed transaction mode
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+        self.conn2._enter_transaction_management(True)
+
+    def tearDown(self):
+        # Close down the second connection.
+        transaction.leave_transaction_management()
+        self.conn2.close()
+
+    @skipUnlessDBFeature('test_db_allows_multiple_connections')
+    def test_concurrent_delete(self):
+        "Deletes on concurrent transactions don't collide and lock the database. Regression for #9479"
+
+        # Create some dummy data
+        b1 = Book(id=1, pagecount=100)
+        b2 = Book(id=2, pagecount=200)
+        b3 = Book(id=3, pagecount=300)
+        b1.save()
+        b2.save()
+        b3.save()
+
+        transaction.commit()
+
+        self.assertEqual(3, Book.objects.count())
+
+        # Delete something using connection 2.
+        cursor2 = self.conn2.cursor()
+        cursor2.execute('DELETE from delete_regress_book WHERE id=1')
+        self.conn2._commit();
+
+        # Now perform a queryset delete that covers the object
+        # deleted in connection 2. This causes an infinite loop
+        # under MySQL InnoDB unless we keep track of already
+        # deleted objects.
+        Book.objects.filter(pagecount__lt=250).delete()
+        transaction.commit()
+        self.assertEqual(1, Book.objects.count())
 
 class DeleteCascadeTests(TestCase):
     def test_generic_relation_cascade(self):

+ 9 - 8
tests/regressiontests/dispatch/tests/test_dispatcher.py

@@ -1,7 +1,8 @@
-from django.dispatch import Signal
-import unittest
-import sys
 import gc
+import sys
+
+from django.dispatch import Signal
+from django.utils import unittest
 import django.utils.copycompat as copy
 
 if sys.platform.startswith('java'):
@@ -20,7 +21,7 @@ def receiver_1_arg(val, **kwargs):
 class Callable(object):
     def __call__(self, val, **kwargs):
         return val
-    
+
     def a(self, val, **kwargs):
         return val
 
@@ -35,7 +36,7 @@ class DispatcherTests(unittest.TestCase):
 
         # force cleanup just in case
         signal.receivers = []
-    
+
     def testExact(self):
         a_signal.connect(receiver_1_arg, sender=self)
         expected = [(receiver_1_arg,"test")]
@@ -51,7 +52,7 @@ class DispatcherTests(unittest.TestCase):
         self.assertEqual(result, expected)
         a_signal.disconnect(receiver_1_arg)
         self._testIsClean(a_signal)
-    
+
     def testGarbageCollected(self):
         a = Callable()
         a_signal.connect(a.a, sender=self)
@@ -61,7 +62,7 @@ class DispatcherTests(unittest.TestCase):
         result = a_signal.send(sender=self, val="test")
         self.assertEqual(result, expected)
         self._testIsClean(a_signal)
-    
+
     def testMultipleRegistration(self):
         a = Callable()
         a_signal.connect(a)
@@ -90,7 +91,7 @@ class DispatcherTests(unittest.TestCase):
         self.assertEqual(len(a_signal.receivers), 1)
         a_signal.disconnect(dispatch_uid = "uid")
         self._testIsClean(a_signal)
-    
+
     def testRobust(self):
         """Test the sendRobust function"""
         def fails(val, **kwargs):

+ 1 - 1
tests/regressiontests/dispatch/tests/test_saferef.py

@@ -1,6 +1,6 @@
 from django.dispatch.saferef import *
 
-import unittest
+from django.utils import unittest
 
 class Test1(object):
     def x(self):

+ 14 - 14
tests/regressiontests/expressions_regress/tests.py

@@ -1,14 +1,14 @@
 """
 Spanning tests for all the operations that F() expressions can perform.
 """
-from django.test import TestCase, Approximate
-
 from django.conf import settings
 from django.db import models, DEFAULT_DB_ALIAS
 from django.db.models import F
+from django.test import TestCase, Approximate, skipUnlessDBFeature
 
 from regressiontests.expressions_regress.models import Number
 
+
 class ExpressionsRegressTests(TestCase):
 
     def setUp(self):
@@ -130,13 +130,13 @@ class ExpressionOperatorTests(TestCase):
         self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 40)
         self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
 
-    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle':
-        def test_lefthand_bitwise_or(self):
-            # LH Bitwise or on integers
-            Number.objects.filter(pk=self.n.pk).update(integer=F('integer') | 48)
+    @skipUnlessDBFeature('supports_bitwise_or')
+    def test_lefthand_bitwise_or(self):
+        # LH Bitwise or on integers
+        Number.objects.filter(pk=self.n.pk).update(integer=F('integer') | 48)
 
-            self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 58)
-            self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+        self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 58)
+        self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
 
     def test_right_hand_addition(self):
         # Right hand operators
@@ -185,11 +185,11 @@ class ExpressionOperatorTests(TestCase):
         self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 10)
         self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
 
-    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle':
-        def test_right_hand_bitwise_or(self):
-            # RH Bitwise or on integers
-            Number.objects.filter(pk=self.n.pk).update(integer=15 | F('integer'))
+    @skipUnlessDBFeature('supports_bitwise_or')
+    def test_right_hand_bitwise_or(self):
+        # RH Bitwise or on integers
+        Number.objects.filter(pk=self.n.pk).update(integer=15 | F('integer'))
 
-            self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 47)
-            self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+        self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 47)
+        self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
 

+ 69 - 66
tests/regressiontests/file_storage/tests.py

@@ -4,7 +4,6 @@ import shutil
 import sys
 import tempfile
 import time
-import unittest
 from cStringIO import StringIO
 from datetime import datetime, timedelta
 from django.conf import settings
@@ -14,7 +13,8 @@ from django.core.files.images import get_image_dimensions
 from django.core.files.storage import FileSystemStorage, get_storage_class
 from django.core.files.uploadedfile import UploadedFile
 from django.core.exceptions import ImproperlyConfigured
-from unittest import TestCase
+from django.utils import unittest
+
 try:
     import threading
 except ImportError:
@@ -294,7 +294,7 @@ class SlowFile(ContentFile):
         time.sleep(1)
         return super(ContentFile, self).chunks()
 
-class FileSaveRaceConditionTest(TestCase):
+class FileSaveRaceConditionTest(unittest.TestCase):
     def setUp(self):
         self.storage_dir = tempfile.mkdtemp()
         self.storage = FileSystemStorage(self.storage_dir)
@@ -315,7 +315,7 @@ class FileSaveRaceConditionTest(TestCase):
         self.storage.delete('conflict')
         self.storage.delete('conflict_1')
 
-class FileStoragePermissions(TestCase):
+class FileStoragePermissions(unittest.TestCase):
     def setUp(self):
         self.old_perms = settings.FILE_UPLOAD_PERMISSIONS
         settings.FILE_UPLOAD_PERMISSIONS = 0666
@@ -332,7 +332,7 @@ class FileStoragePermissions(TestCase):
         self.assertEqual(actual_mode, 0666)
 
 
-class FileStoragePathParsing(TestCase):
+class FileStoragePathParsing(unittest.TestCase):
     def setUp(self):
         self.storage_dir = tempfile.mkdtemp()
         self.storage = FileSystemStorage(self.storage_dir)
@@ -370,64 +370,67 @@ class FileStoragePathParsing(TestCase):
         else:
             self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
 
-if Image is not None:
-    class DimensionClosingBug(TestCase):
-        """
-        Test that get_image_dimensions() properly closes files (#8817)
-        """
-        def test_not_closing_of_files(self):
-            """
-            Open files passed into get_image_dimensions() should stay opened.
-            """
-            empty_io = StringIO()
-            try:
-                get_image_dimensions(empty_io)
-            finally:
-                self.assert_(not empty_io.closed)
-
-        def test_closing_of_filenames(self):
-            """
-            get_image_dimensions() called with a filename should closed the file.
-            """
-            # We need to inject a modified open() builtin into the images module
-            # that checks if the file was closed properly if the function is
-            # called with a filename instead of an file object.
-            # get_image_dimensions will call our catching_open instead of the
-            # regular builtin one.
-
-            class FileWrapper(object):
-                _closed = []
-                def __init__(self, f):
-                    self.f = f
-                def __getattr__(self, name):
-                    return getattr(self.f, name)
-                def close(self):
-                    self._closed.append(True)
-                    self.f.close()
-
-            def catching_open(*args):
-                return FileWrapper(open(*args))
-
-            from django.core.files import images
-            images.open = catching_open
-            try:
-                get_image_dimensions(os.path.join(os.path.dirname(__file__), "test1.png"))
-            finally:
-                del images.open
-            self.assert_(FileWrapper._closed)
-
-    class InconsistentGetImageDimensionsBug(TestCase):
-        """
-        Test that get_image_dimensions() works properly after various calls using a file handler (#11158)
-        """
-        def test_multiple_calls(self):
-            """
-            Multiple calls of get_image_dimensions() should return the same size.
-            """
-            from django.core.files.images import ImageFile
-            img_path = os.path.join(os.path.dirname(__file__), "test.png")
-            image = ImageFile(open(img_path, 'rb'))
-            image_pil = Image.open(img_path)
-            size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image)
-            self.assertEqual(image_pil.size, size_1)
-            self.assertEqual(size_1, size_2)
+class DimensionClosingBug(unittest.TestCase):
+    """
+    Test that get_image_dimensions() properly closes files (#8817)
+    """
+    @unittest.skipUnless(Image, "PIL not installed")
+    def test_not_closing_of_files(self):
+        """
+        Open files passed into get_image_dimensions() should stay opened.
+        """
+        empty_io = StringIO()
+        try:
+            get_image_dimensions(empty_io)
+        finally:
+            self.assert_(not empty_io.closed)
+
+    @unittest.skipUnless(Image, "PIL not installed")
+    def test_closing_of_filenames(self):
+        """
+        get_image_dimensions() called with a filename should closed the file.
+        """
+        # We need to inject a modified open() builtin into the images module
+        # that checks if the file was closed properly if the function is
+        # called with a filename instead of an file object.
+        # get_image_dimensions will call our catching_open instead of the
+        # regular builtin one.
+
+        class FileWrapper(object):
+            _closed = []
+            def __init__(self, f):
+                self.f = f
+            def __getattr__(self, name):
+                return getattr(self.f, name)
+            def close(self):
+                self._closed.append(True)
+                self.f.close()
+
+        def catching_open(*args):
+            return FileWrapper(open(*args))
+
+        from django.core.files import images
+        images.open = catching_open
+        try:
+            get_image_dimensions(os.path.join(os.path.dirname(__file__), "test1.png"))
+        finally:
+            del images.open
+        self.assert_(FileWrapper._closed)
+
+class InconsistentGetImageDimensionsBug(unittest.TestCase):
+    """
+    Test that get_image_dimensions() works properly after various calls
+    using a file handler (#11158)
+    """
+    @unittest.skipUnless(Image, "PIL not installed")
+    def test_multiple_calls(self):
+        """
+        Multiple calls of get_image_dimensions() should return the same size.
+        """
+        from django.core.files.images import ImageFile
+        img_path = os.path.join(os.path.dirname(__file__), "test.png")
+        image = ImageFile(open(img_path, 'rb'))
+        image_pil = Image.open(img_path)
+        size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image)
+        self.assertEqual(image_pil.size, size_1)
+        self.assertEqual(size_1, size_2)

+ 4 - 4
tests/regressiontests/file_uploads/tests.py

@@ -2,15 +2,15 @@
 import os
 import errno
 import shutil
-import unittest
 from StringIO import StringIO
 
 from django.core.files import temp as tempfile
 from django.core.files.uploadedfile import SimpleUploadedFile
+from django.http.multipartparser import MultiPartParser
 from django.test import TestCase, client
 from django.utils import simplejson
+from django.utils import unittest
 from django.utils.hashcompat import sha_constructor
-from django.http.multipartparser import MultiPartParser
 
 from models import FileModel, temp_storage, UPLOAD_TO
 import uploadhandler
@@ -223,7 +223,7 @@ class FileUploadTests(TestCase):
                 ret = super(POSTAccessingHandler, self).handle_uncaught_exception(request, resolver, exc_info)
                 p = request.POST
                 return ret
-        
+
         post_data = {
             'name': 'Ringo',
             'file_field': open(__file__),
@@ -244,7 +244,7 @@ class FileUploadTests(TestCase):
             response = self.client.post('/file_uploads/upload_errors/', post_data)
         except reference_error.__class__, err:
             self.failIf(
-                str(err) == str(reference_error), 
+                str(err) == str(reference_error),
                 "Caught a repeated exception that'll cause an infinite loop in file uploads."
             )
         except Exception, err:

+ 2 - 2
tests/regressiontests/fixtures_regress/models.py

@@ -1,4 +1,4 @@
-from django.db import models, DEFAULT_DB_ALIAS
+from django.db import models, DEFAULT_DB_ALIAS, connection
 from django.contrib.auth.models import User
 from django.conf import settings
 
@@ -31,7 +31,7 @@ class Stuff(models.Model):
         # Oracle doesn't distinguish between None and the empty string.
         # This hack makes the test case pass using Oracle.
         name = self.name
-        if (settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle'
+        if (connection.features.interprets_empty_strings_as_nulls
             and name == u''):
             name = None
         return unicode(name) + u' is owned by ' + unicode(self.owner)

+ 1 - 2
tests/regressiontests/forms/fields.py

@@ -30,11 +30,10 @@ import re
 import os
 from decimal import Decimal
 
-from unittest import TestCase
-
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.forms import *
 from django.forms.widgets import RadioFieldRenderer
+from django.utils.unittest import TestCase
 
 
 def fix_os_paths(x):

+ 1 - 1
tests/regressiontests/forms/input_formats.py

@@ -1,9 +1,9 @@
 from datetime import time, date, datetime
-from unittest import TestCase
 
 from django import forms
 from django.conf import settings
 from django.utils.translation import activate, deactivate
+from django.utils.unittest import TestCase
 
 
 class LocalizedTimeTests(TestCase):

+ 1 - 2
tests/regressiontests/forms/validators.py

@@ -1,8 +1,7 @@
-from unittest import TestCase
-
 from django import forms
 from django.core import validators
 from django.core.exceptions import ValidationError
+from django.utils.unittest import TestCase
 
 
 class TestFieldWithValidators(TestCase):

+ 2 - 3
tests/regressiontests/forms/widgets.py

@@ -1265,10 +1265,9 @@ u'<input type="hidden" name="date_0" value="17.09.2007" /><input type="hidden" n
 
 """
 
-
-from django.utils import copycompat as copy
-from unittest import TestCase
 from django import forms
+from django.utils import copycompat as copy
+from django.utils.unittest import TestCase
 from django.core.files.uploadedfile import SimpleUploadedFile
 
 

+ 25 - 24
tests/regressiontests/httpwrappers/tests.py

@@ -1,7 +1,8 @@
 import copy
 import pickle
-import unittest
+
 from django.http import QueryDict, HttpResponse, CompatCookie, BadHeaderError
+from django.utils import unittest
 
 class QueryDictTests(unittest.TestCase):
     def test_missing_key(self):
@@ -17,7 +18,7 @@ class QueryDictTests(unittest.TestCase):
         self.assertRaises(AttributeError, q.pop, 'foo')
         self.assertRaises(AttributeError, q.popitem)
         self.assertRaises(AttributeError, q.clear)
-        
+
     def test_immutable_get_with_default(self):
         q = QueryDict('')
         self.assertEqual(q.get('foo', 'default'), 'default')
@@ -34,7 +35,7 @@ class QueryDictTests(unittest.TestCase):
         self.assertEqual(q.values(), [])
         self.assertEqual(len(q), 0)
         self.assertEqual(q.urlencode(), '')
-        
+
     def test_single_key_value(self):
         """Test QueryDict with one key/value pair"""
 
@@ -47,7 +48,7 @@ class QueryDictTests(unittest.TestCase):
         self.assertEqual(q.get('bar', 'default'), 'default')
         self.assertEqual(q.getlist('foo'), ['bar'])
         self.assertEqual(q.getlist('bar'), [])
-        
+
         self.assertRaises(AttributeError, q.setlist, 'foo', ['bar'])
         self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
 
@@ -67,16 +68,16 @@ class QueryDictTests(unittest.TestCase):
         self.assertRaises(AttributeError, q.popitem)
         self.assertRaises(AttributeError, q.clear)
         self.assertRaises(AttributeError, q.setdefault, 'foo', 'bar')
-        
+
         self.assertEqual(q.urlencode(), 'foo=bar')
-        
+
     def test_mutable_copy(self):
         """A copy of a QueryDict is mutable."""
         q = QueryDict('').copy()
         self.assertRaises(KeyError, q.__getitem__, "foo")
         q['name'] = 'john'
         self.assertEqual(q['name'], 'john')
-        
+
     def test_mutable_delete(self):
         q = QueryDict('').copy()
         q['name'] = 'john'
@@ -126,20 +127,20 @@ class QueryDictTests(unittest.TestCase):
         """Test QueryDict with two key/value pairs with same keys."""
 
         q = QueryDict('vote=yes&vote=no')
-        
+
         self.assertEqual(q['vote'], u'no')
         self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
-                
+
         self.assertEqual(q.get('vote', 'default'), u'no')
         self.assertEqual(q.get('foo', 'default'), 'default')
         self.assertEqual(q.getlist('vote'), [u'yes', u'no'])
         self.assertEqual(q.getlist('foo'), [])
-        
+
         self.assertRaises(AttributeError, q.setlist, 'foo', ['bar', 'baz'])
         self.assertRaises(AttributeError, q.setlist, 'foo', ['bar', 'baz'])
         self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
 
-        self.assertEqual(q.has_key('vote'), True)        
+        self.assertEqual(q.has_key('vote'), True)
         self.assertEqual('vote' in q, True)
         self.assertEqual(q.has_key('foo'), False)
         self.assertEqual('foo' in q, False)
@@ -148,23 +149,23 @@ class QueryDictTests(unittest.TestCase):
         self.assertEqual(q.keys(), [u'vote'])
         self.assertEqual(q.values(), [u'no'])
         self.assertEqual(len(q), 1)
-        
+
         self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
         self.assertRaises(AttributeError, q.pop, 'foo')
         self.assertRaises(AttributeError, q.popitem)
         self.assertRaises(AttributeError, q.clear)
         self.assertRaises(AttributeError, q.setdefault, 'foo', 'bar')
         self.assertRaises(AttributeError, q.__delitem__, 'vote')
-        
+
     def test_invalid_input_encoding(self):
         """
         QueryDicts must be able to handle invalid input encoding (in this
         case, bad UTF-8 encoding).
         """
         q = QueryDict('foo=bar&foo=\xff')
-        self.assertEqual(q['foo'], u'\ufffd')        
+        self.assertEqual(q['foo'], u'\ufffd')
         self.assertEqual(q.getlist('foo'), [u'bar', u'\ufffd'])
-   
+
     def test_pickle(self):
         q = QueryDict('')
         q1 = pickle.loads(pickle.dumps(q, 2))
@@ -172,7 +173,7 @@ class QueryDictTests(unittest.TestCase):
         q = QueryDict('a=b&c=d')
         q1 = pickle.loads(pickle.dumps(q, 2))
         self.assertEqual(q == q1, True)
-        q = QueryDict('a=b&c=d&a=1') 
+        q = QueryDict('a=b&c=d&a=1')
         q1 = pickle.loads(pickle.dumps(q, 2))
         self.assertEqual(q == q1 , True)
 
@@ -181,21 +182,21 @@ class QueryDictTests(unittest.TestCase):
         x = QueryDict("a=1&a=2", mutable=True)
         y = QueryDict("a=3&a=4")
         x.update(y)
-        self.assertEqual(x.getlist('a'), [u'1', u'2', u'3', u'4'])    
+        self.assertEqual(x.getlist('a'), [u'1', u'2', u'3', u'4'])
 
     def test_non_default_encoding(self):
         """#13572 - QueryDict with a non-default encoding"""
-        q = QueryDict('sbb=one', encoding='rot_13') 
+        q = QueryDict('sbb=one', encoding='rot_13')
         self.assertEqual(q.encoding , 'rot_13' )
         self.assertEqual(q.items() , [(u'foo', u'bar')] )
         self.assertEqual(q.urlencode() , 'sbb=one' )
-        q = q.copy() 
+        q = q.copy()
         self.assertEqual(q.encoding , 'rot_13' )
         self.assertEqual(q.items() , [(u'foo', u'bar')] )
         self.assertEqual(q.urlencode() , 'sbb=one' )
         self.assertEqual(copy.copy(q).encoding , 'rot_13' )
         self.assertEqual(copy.deepcopy(q).encoding , 'rot_13')
-        
+
 class HttpResponseTests(unittest.TestCase):
     def test_unicode_headers(self):
         r = HttpResponse()
@@ -203,10 +204,10 @@ class HttpResponseTests(unittest.TestCase):
         # If we insert a unicode value it will be converted to an ascii
         r['value'] = u'test value'
         self.failUnless(isinstance(r['value'], str))
-        
+
         # An error is raised ~hen a unicode object with non-ascii is assigned.
         self.assertRaises(UnicodeEncodeError, r.__setitem__, 'value', u't\xebst value')
-        
+
         # An error is raised when  a unicode object with non-ASCII format is
         # passed as initial mimetype or content_type.
         self.assertRaises(UnicodeEncodeError, HttpResponse,
@@ -216,12 +217,12 @@ class HttpResponseTests(unittest.TestCase):
         self.assertRaises(UnicodeEncodeError, HttpResponse,
                 content_type=u't\xebst value')
 
-        # The response also converts unicode keys to strings.)      
+        # The response also converts unicode keys to strings.)
         r[u'test'] = 'testing key'
         l = list(r.items())
         l.sort()
         self.assertEqual(l[1], ('test', 'testing key'))
-        
+
         # It will also raise errors for keys with non-ascii data.
         self.assertRaises(UnicodeEncodeError, r.__setitem__, u't\xebst key', 'value')
 

+ 2 - 1
tests/regressiontests/humanize/tests.py

@@ -1,6 +1,7 @@
-import unittest
 from datetime import timedelta, date
+
 from django.template import Template, Context, add_to_builtins
+from django.utils import unittest
 from django.utils.dateformat import DateFormat
 from django.utils.translation import ugettext as _
 from django.utils.html import escape

+ 8 - 8
tests/regressiontests/introspection/tests.py

@@ -1,6 +1,6 @@
 from django.conf import settings
 from django.db import connection, DEFAULT_DB_ALIAS
-from django.test import TestCase
+from django.test import TestCase, skipUnlessDBFeature
 from django.utils import functional
 
 from models import Reporter, Article
@@ -77,13 +77,13 @@ class IntrospectionTests(TestCase):
         )
 
     # Regression test for #9991 - 'real' types in postgres
-    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].startswith('django.db.backends.postgresql'):
-        def test_postgresql_real_type(self):
-            cursor = connection.cursor()
-            cursor.execute("CREATE TABLE django_ixn_real_test_table (number REAL);")
-            desc = connection.introspection.get_table_description(cursor, 'django_ixn_real_test_table')
-            cursor.execute('DROP TABLE django_ixn_real_test_table;')
-            self.assertEqual(datatype(desc[0][1], desc[0]), 'FloatField')
+    @skipUnlessDBFeature('has_real_datatype')
+    def test_postgresql_real_type(self):
+        cursor = connection.cursor()
+        cursor.execute("CREATE TABLE django_ixn_real_test_table (number REAL);")
+        desc = connection.introspection.get_table_description(cursor, 'django_ixn_real_test_table')
+        cursor.execute('DROP TABLE django_ixn_real_test_table;')
+        self.assertEqual(datatype(desc[0][1], desc[0]), 'FloatField')
 
     def test_get_relations(self):
         cursor = connection.cursor()

+ 1 - 1
tests/regressiontests/localflavor/tests.py

@@ -1,5 +1,5 @@
-import unittest
 from django.test import TestCase
+from django.utils import unittest
 
 # just import your tests here
 from us.tests import *

+ 5 - 5
tests/regressiontests/max_lengths/tests.py

@@ -1,12 +1,12 @@
-from unittest import TestCase
 from django.db import DatabaseError
+from django.utils import unittest
 from regressiontests.max_lengths.models import PersonWithDefaultMaxLengths, PersonWithCustomMaxLengths
 
-class MaxLengthArgumentsTests(TestCase):
-        
+class MaxLengthArgumentsTests(unittest.TestCase):
+
     def verify_max_length(self, model,field,length):
         self.assertEquals(model._meta.get_field(field).max_length,length)
-        
+
     def test_default_max_lengths(self):
         self.verify_max_length(PersonWithDefaultMaxLengths, 'email', 75)
         self.verify_max_length(PersonWithDefaultMaxLengths, 'vcard', 100)
@@ -19,7 +19,7 @@ class MaxLengthArgumentsTests(TestCase):
         self.verify_max_length(PersonWithCustomMaxLengths, 'homepage', 250)
         self.verify_max_length(PersonWithCustomMaxLengths, 'avatar', 250)
 
-class MaxLengthORMTests(TestCase):
+class MaxLengthORMTests(unittest.TestCase):
 
     def test_custom_max_lengths(self):
         args = {

+ 11 - 11
tests/regressiontests/model_fields/tests.py

@@ -1,12 +1,12 @@
 import datetime
-import unittest
 from decimal import Decimal
 
-import django.test
+from django import test
 from django import forms
-from django.db import models
 from django.core.exceptions import ValidationError
+from django.db import models
 from django.db.models.fields.files import FieldFile
+from django.utils import unittest
 
 from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel, Document
 
@@ -22,7 +22,7 @@ if Image:
             TwoImageFieldTests
 
 
-class BasicFieldTests(django.test.TestCase):
+class BasicFieldTests(test.TestCase):
     def test_show_hidden_initial(self):
         """
         Regression test for #12913. Make sure fields with choices respect
@@ -48,7 +48,7 @@ class BasicFieldTests(django.test.TestCase):
         except ValidationError, e:
             self.fail("NullBooleanField failed validation with value of None: %s" % e.messages)
 
-class DecimalFieldTests(django.test.TestCase):
+class DecimalFieldTests(test.TestCase):
     def test_to_python(self):
         f = models.DecimalField(max_digits=4, decimal_places=2)
         self.assertEqual(f.to_python(3), Decimal("3"))
@@ -95,7 +95,7 @@ class DecimalFieldTests(django.test.TestCase):
         # This should not crash. That counts as a win for our purposes.
         Foo.objects.filter(d__gte=100000000000)
 
-class ForeignKeyTests(django.test.TestCase):
+class ForeignKeyTests(test.TestCase):
     def test_callable_default(self):
         """Test the use of a lazy callable for ForeignKey.default"""
         a = Foo.objects.create(id=1, a='abc', d=Decimal("12.34"))
@@ -194,7 +194,7 @@ class BooleanFieldTests(unittest.TestCase):
             select={'string_length': 'LENGTH(string)'})[0]
         self.assertFalse(isinstance(b5.pk, bool))
 
-class ChoicesTests(django.test.TestCase):
+class ChoicesTests(test.TestCase):
     def test_choices_and_field_display(self):
         """
         Check that get_choices and get_flatchoices interact with
@@ -206,7 +206,7 @@ class ChoicesTests(django.test.TestCase):
         self.assertEqual(Whiz(c=None).get_c_display(), None)    # Blank value
         self.assertEqual(Whiz(c='').get_c_display(), '')        # Empty value
 
-class SlugFieldTests(django.test.TestCase):
+class SlugFieldTests(test.TestCase):
     def test_slugfield_max_length(self):
         """
         Make sure SlugField honors max_length (#9706)
@@ -216,7 +216,7 @@ class SlugFieldTests(django.test.TestCase):
         self.assertEqual(bs.s, 'slug'*50)
 
 
-class ValidationTest(django.test.TestCase):
+class ValidationTest(test.TestCase):
     def test_charfield_raises_error_on_empty_string(self):
         f = models.CharField()
         self.assertRaises(ValidationError, f.clean, "", None)
@@ -271,7 +271,7 @@ class ValidationTest(django.test.TestCase):
         self.assertRaises(ValidationError, f.clean, None, None)
 
 
-class BigIntegerFieldTests(django.test.TestCase):
+class BigIntegerFieldTests(test.TestCase):
     def test_limits(self):
         # Ensure that values that are right at the limits can be saved
         # and then retrieved without corruption.
@@ -299,7 +299,7 @@ class BigIntegerFieldTests(django.test.TestCase):
         b = BigInt.objects.get(value = '10')
         self.assertEqual(b.value, 10)
 
-class TypeCoercionTests(django.test.TestCase):
+class TypeCoercionTests(test.TestCase):
     """
     Test that database lookups can accept the wrong types and convert
     them with no error: especially on Postgres 8.3+ which does not do

+ 2 - 4
tests/regressiontests/model_regress/models.py

@@ -2,7 +2,7 @@
 import datetime
 
 from django.conf import settings
-from django.db import models, DEFAULT_DB_ALIAS
+from django.db import models, DEFAULT_DB_ALIAS, connection
 from django.utils import tzinfo
 
 CHOICES = (
@@ -149,9 +149,7 @@ datetime.datetime(2000, 1, 1, 6, 1, 1)
 
 """}
 
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] not in (
-        "django.db.backends.mysql",
-        "django.db.backends.oracle"):
+if connection.features.supports_timezones:
     __test__["timezone-tests"] = """
 # Saving an updating with timezone-aware datetime Python objects. Regression
 # test for #10443.

+ 1 - 2
tests/regressiontests/pagination_regress/tests.py

@@ -1,6 +1,5 @@
-from unittest import TestCase
-
 from django.core.paginator import Paginator, EmptyPage
+from django.utils.unittest import TestCase
 
 class PaginatorTests(TestCase):
     """

+ 4 - 4
tests/regressiontests/queries/models.py

@@ -8,7 +8,7 @@ import sys
 import threading
 
 from django.conf import settings
-from django.db import models, DEFAULT_DB_ALIAS
+from django.db import models, DEFAULT_DB_ALIAS, connection
 from django.db.models import Count
 from django.db.models.query import Q, ITER_CHUNK_SIZE, EmptyQuerySet
 
@@ -1307,13 +1307,13 @@ FieldError: Infinite loop caused by ordering.
 
 
 # In Oracle, we expect a null CharField to return u'' instead of None.
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.oracle":
+if connection.features.interprets_empty_strings_as_nulls:
     __test__["API_TESTS"] = __test__["API_TESTS"].replace("<NONE_OR_EMPTY_UNICODE>", "u''")
 else:
     __test__["API_TESTS"] = __test__["API_TESTS"].replace("<NONE_OR_EMPTY_UNICODE>", "None")
 
 
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.mysql":
+if connection.features.requires_explicit_null_ordering_when_grouping:
     __test__["API_TESTS"] += """
 When grouping without specifying ordering, we add an explicit "ORDER BY NULL"
 portion in MySQL to prevent unnecessary sorting.
@@ -1342,7 +1342,7 @@ Using an empty generator expression as the rvalue for an "__in" lookup is legal
 
 # Sqlite 3 does not support passing in more than 1000 parameters except by
 # changing a parameter at compilation time.
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != "django.db.backends.sqlite3":
+if connection.features.supports_1000_query_paramters:
     __test__["API_TESTS"] += """
 Bug #14244: Test that the "in" lookup works with lists of 1000 items or more.
 >>> Number.objects.all().delete()

+ 1 - 2
tests/regressiontests/queries/tests.py

@@ -1,8 +1,7 @@
-import unittest
-
 from django.db import DatabaseError, connections, DEFAULT_DB_ALIAS
 from django.db.models import Count
 from django.test import TestCase
+from django.utils import unittest
 
 from models import Tag, Annotation, DumbCategory, Note, ExtraInfo, Number
 

+ 3 - 3
tests/regressiontests/serializers_regress/tests.py

@@ -17,7 +17,7 @@ except ImportError:
 
 from django.conf import settings
 from django.core import serializers, management
-from django.db import transaction, DEFAULT_DB_ALIAS
+from django.db import transaction, DEFAULT_DB_ALIAS, connection
 from django.test import TestCase
 from django.utils.functional import curry
 
@@ -335,7 +335,7 @@ The end."""),
 # Because Oracle treats the empty string as NULL, Oracle is expected to fail
 # when field.empty_strings_allowed is True and the value is None; skip these
 # tests.
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+if connection.features.interprets_empty_strings_as_nulls:
     test_data = [data for data in test_data
                  if not (data[0] == data_obj and
                          data[2]._meta.get_field('data').empty_strings_allowed and
@@ -344,7 +344,7 @@ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle'
 # Regression test for #8651 -- a FK to an object iwth PK of 0
 # This won't work on MySQL since it won't let you create an object
 # with a primary key of 0,
-if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql':
+if connection.features.allows_primary_key_0:
     test_data.extend([
         (data_obj, 0, Anchor, "Anchor 0"),
         (fk_obj, 465, FKData, 0),

+ 1 - 1
tests/regressiontests/settings_tests/tests.py

@@ -1,5 +1,5 @@
-import unittest
 from django.conf import settings
+from django.utils import unittest
 
 class SettingsTests(unittest.TestCase):
 

+ 3 - 3
tests/regressiontests/templates/loaders.py

@@ -9,7 +9,6 @@ from django.conf import settings
 if __name__ == '__main__':
     settings.configure()
 
-import unittest
 import sys
 import pkg_resources
 import imp
@@ -21,6 +20,8 @@ from django.template import TemplateDoesNotExist, Context
 from django.template.loaders.eggs import load_template_source as lts_egg
 from django.template.loaders.eggs import Loader as EggLoader
 from django.template import loader
+from django.utils import unittest
+
 
 # Mock classes and objects for pkg_resources functions.
 class MockProvider(pkg_resources.NullProvider):
@@ -73,6 +74,7 @@ class DeprecatedEggLoaderTest(unittest.TestCase):
     def tearDown(self):
         settings.INSTALLED_APPS = self._old_installed_apps
         warnings.resetwarnings()
+        warnings.simplefilter("ignore", PendingDeprecationWarning)
 
     def test_existing(self):
         "A template can be loaded from an egg"
@@ -80,8 +82,6 @@ class DeprecatedEggLoaderTest(unittest.TestCase):
         contents, template_name = lts_egg("y.html")
         self.assertEqual(contents, "y")
         self.assertEqual(template_name, "egg:egg_1:templates/y.html")
-        warnings.resetwarnings()
-        warnings.simplefilter("ignore", PendingDeprecationWarning)
 
 
 class EggLoaderTest(unittest.TestCase):

+ 1 - 1
tests/regressiontests/templates/nodelist.py

@@ -1,6 +1,6 @@
-from unittest import TestCase
 from django.template.loader import get_template_from_string
 from django.template import VariableNode
+from django.utils.unittest import TestCase
 
 
 class NodelistTest(TestCase):

+ 1 - 1
tests/regressiontests/templates/smartif.py

@@ -1,5 +1,5 @@
-import unittest
 from django.template.smartif import IfParser, Literal
+from django.utils import unittest
 
 class SmartIfTests(unittest.TestCase):
 

+ 30 - 30
tests/regressiontests/templates/tests.py

@@ -11,12 +11,12 @@ import time
 import os
 import sys
 import traceback
-import unittest
 
 from django import template
 from django.core import urlresolvers
 from django.template import loader
 from django.template.loaders import app_directories, filesystem, cached
+from django.utils import unittest
 from django.utils.translation import activate, deactivate, ugettext as _
 from django.utils.safestring import mark_safe
 from django.utils.tzinfo import LocalTimezone
@@ -506,16 +506,16 @@ class Templates(unittest.TestCase):
             'basic-syntax28': ("{{ a.b }}", {'a': SilentGetItemClass()}, ('', 'INVALID')),
             'basic-syntax29': ("{{ a.b }}", {'a': SilentAttrClass()}, ('', 'INVALID')),
 
-            # Something that starts like a number but has an extra lookup works as a lookup. 
-            'basic-syntax30': ("{{ 1.2.3 }}", {"1": {"2": {"3": "d"}}}, "d"), 
-            'basic-syntax31': ("{{ 1.2.3 }}", {"1": {"2": ("a", "b", "c", "d")}}, "d"), 
-            'basic-syntax32': ("{{ 1.2.3 }}", {"1": (("x", "x", "x", "x"), ("y", "y", "y", "y"), ("a", "b", "c", "d"))}, "d"), 
-            'basic-syntax33': ("{{ 1.2.3 }}", {"1": ("xxxx", "yyyy", "abcd")}, "d"), 
-            'basic-syntax34': ("{{ 1.2.3 }}", {"1": ({"x": "x"}, {"y": "y"}, {"z": "z", "3": "d"})}, "d"), 
-            
-            # Numbers are numbers even if their digits are in the context. 
-            'basic-syntax35': ("{{ 1 }}", {"1": "abc"}, "1"), 
-            'basic-syntax36': ("{{ 1.2 }}", {"1": "abc"}, "1.2"), 
+            # Something that starts like a number but has an extra lookup works as a lookup.
+            'basic-syntax30': ("{{ 1.2.3 }}", {"1": {"2": {"3": "d"}}}, "d"),
+            'basic-syntax31': ("{{ 1.2.3 }}", {"1": {"2": ("a", "b", "c", "d")}}, "d"),
+            'basic-syntax32': ("{{ 1.2.3 }}", {"1": (("x", "x", "x", "x"), ("y", "y", "y", "y"), ("a", "b", "c", "d"))}, "d"),
+            'basic-syntax33': ("{{ 1.2.3 }}", {"1": ("xxxx", "yyyy", "abcd")}, "d"),
+            'basic-syntax34': ("{{ 1.2.3 }}", {"1": ({"x": "x"}, {"y": "y"}, {"z": "z", "3": "d"})}, "d"),
+
+            # Numbers are numbers even if their digits are in the context.
+            'basic-syntax35': ("{{ 1 }}", {"1": "abc"}, "1"),
+            'basic-syntax36': ("{{ 1.2 }}", {"1": "abc"}, "1.2"),
 
             # List-index syntax allows a template to access a certain item of a subscriptable object.
             'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"),
@@ -603,7 +603,7 @@ class Templates(unittest.TestCase):
 
             #filters should accept empty string constants
             'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""),
-            
+
             ### COMMENT SYNTAX ########################################################
             'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
             'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"),
@@ -1300,8 +1300,8 @@ class Templates(unittest.TestCase):
 
             # Regression test for #11270.
             'cache17': ('{% load cache %}{% cache 10 long_cache_key poem %}Some Content{% endcache %}', {'poem': 'Oh freddled gruntbuggly/Thy micturations are to me/As plurdled gabbleblotchits/On a lurgid bee/That mordiously hath bitled out/Its earted jurtles/Into a rancid festering/Or else I shall rend thee in the gobberwarts with my blurglecruncheon/See if I dont.'}, 'Some Content'),
-                                    
-            
+
+
             ### AUTOESCAPE TAG ##############################################
             'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),
             'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
@@ -1330,23 +1330,23 @@ class Templates(unittest.TestCase):
             # implementation details (fortunately, the (no)autoescape block
             # tags can be used in those cases)
             'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError),
-        
+
             # ifqeual compares unescaped vales.
-            'autoescape-ifequal01': ('{% ifequal var "this & that" %}yes{% endifequal %}', { "var": "this & that" }, "yes" ), 
-            
-            # Arguments to filters are 'safe' and manipulate their input unescaped. 
-            'autoescape-filters01': ('{{ var|cut:"&" }}', { "var": "this & that" }, "this  that" ), 
-            'autoescape-filters02': ('{{ var|join:" & \" }}', { "var": ("Tom", "Dick", "Harry") }, "Tom & Dick & Harry" ), 
-            
-            # Literal strings are safe. 
-            'autoescape-literals01': ('{{ "this & that" }}',{}, "this & that" ), 
-            
-            # Iterating over strings outputs safe characters. 
-            'autoescape-stringiterations01': ('{% for l in var %}{{ l }},{% endfor %}', {'var': 'K&R'}, "K,&amp;,R," ), 
-            
-            # Escape requirement survives lookup. 
-            'autoescape-lookup01': ('{{ var.key }}', { "var": {"key": "this & that" }}, "this &amp; that" ), 
-                    
+            'autoescape-ifequal01': ('{% ifequal var "this & that" %}yes{% endifequal %}', { "var": "this & that" }, "yes" ),
+
+            # Arguments to filters are 'safe' and manipulate their input unescaped.
+            'autoescape-filters01': ('{{ var|cut:"&" }}', { "var": "this & that" }, "this  that" ),
+            'autoescape-filters02': ('{{ var|join:" & \" }}', { "var": ("Tom", "Dick", "Harry") }, "Tom & Dick & Harry" ),
+
+            # Literal strings are safe.
+            'autoescape-literals01': ('{{ "this & that" }}',{}, "this & that" ),
+
+            # Iterating over strings outputs safe characters.
+            'autoescape-stringiterations01': ('{% for l in var %}{{ l }},{% endfor %}', {'var': 'K&R'}, "K,&amp;,R," ),
+
+            # Escape requirement survives lookup.
+            'autoescape-lookup01': ('{{ var.key }}', { "var": {"key": "this & that" }}, "this &amp; that" ),
+
         }
 
 

+ 48 - 48
tests/regressiontests/test_client_regress/models.py

@@ -35,83 +35,83 @@ class AssertContainsTests(TestCase):
         try:
             self.assertContains(response, 'text', status_code=999)
         except AssertionError, e:
-            self.assertEquals(str(e), "Couldn't retrieve content: Response code was 200 (expected 999)")
+            self.assertIn("Couldn't retrieve content: Response code was 200 (expected 999)", str(e))
         try:
             self.assertContains(response, 'text', status_code=999, msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Couldn't retrieve content: Response code was 200 (expected 999)")
+            self.assertIn("abc: Couldn't retrieve content: Response code was 200 (expected 999)", str(e))
 
         try:
             self.assertNotContains(response, 'text', status_code=999)
         except AssertionError, e:
-            self.assertEquals(str(e), "Couldn't retrieve content: Response code was 200 (expected 999)")
+            self.assertIn("Couldn't retrieve content: Response code was 200 (expected 999)", str(e))
         try:
             self.assertNotContains(response, 'text', status_code=999, msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Couldn't retrieve content: Response code was 200 (expected 999)")
+            self.assertIn("abc: Couldn't retrieve content: Response code was 200 (expected 999)", str(e))
 
         try:
             self.assertNotContains(response, 'once')
         except AssertionError, e:
-            self.assertEquals(str(e), "Response should not contain 'once'")
+            self.assertIn("Response should not contain 'once'", str(e))
         try:
             self.assertNotContains(response, 'once', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Response should not contain 'once'")
+            self.assertIn("abc: Response should not contain 'once'", str(e))
 
         try:
             self.assertContains(response, 'never', 1)
         except AssertionError, e:
-            self.assertEquals(str(e), "Found 0 instances of 'never' in response (expected 1)")
+            self.assertIn("Found 0 instances of 'never' in response (expected 1)", str(e))
         try:
             self.assertContains(response, 'never', 1, msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Found 0 instances of 'never' in response (expected 1)")
+            self.assertIn("abc: Found 0 instances of 'never' in response (expected 1)", str(e))
 
         try:
             self.assertContains(response, 'once', 0)
         except AssertionError, e:
-            self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 0)")
+            self.assertIn("Found 1 instances of 'once' in response (expected 0)", str(e))
         try:
             self.assertContains(response, 'once', 0, msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Found 1 instances of 'once' in response (expected 0)")
+            self.assertIn("abc: Found 1 instances of 'once' in response (expected 0)", str(e))
 
         try:
             self.assertContains(response, 'once', 2)
         except AssertionError, e:
-            self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 2)")
+            self.assertIn("Found 1 instances of 'once' in response (expected 2)", str(e))
         try:
             self.assertContains(response, 'once', 2, msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Found 1 instances of 'once' in response (expected 2)")
+            self.assertIn("abc: Found 1 instances of 'once' in response (expected 2)", str(e))
 
         try:
             self.assertContains(response, 'twice', 1)
         except AssertionError, e:
-            self.assertEquals(str(e), "Found 2 instances of 'twice' in response (expected 1)")
+            self.assertIn("Found 2 instances of 'twice' in response (expected 1)", str(e))
         try:
             self.assertContains(response, 'twice', 1, msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Found 2 instances of 'twice' in response (expected 1)")
+            self.assertIn("abc: Found 2 instances of 'twice' in response (expected 1)", str(e))
 
         try:
             self.assertContains(response, 'thrice')
         except AssertionError, e:
-            self.assertEquals(str(e), "Couldn't find 'thrice' in response")
+            self.assertIn("Couldn't find 'thrice' in response", str(e))
         try:
             self.assertContains(response, 'thrice', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Couldn't find 'thrice' in response")
+            self.assertIn("abc: Couldn't find 'thrice' in response", str(e))
 
         try:
             self.assertContains(response, 'thrice', 3)
         except AssertionError, e:
-            self.assertEquals(str(e), "Found 0 instances of 'thrice' in response (expected 3)")
+            self.assertIn("Found 0 instances of 'thrice' in response (expected 3)", str(e))
         try:
             self.assertContains(response, 'thrice', 3, msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Found 0 instances of 'thrice' in response (expected 3)")
+            self.assertIn("abc: Found 0 instances of 'thrice' in response (expected 3)", str(e))
 
     def test_unicode_contains(self):
         "Unicode characters can be found in template context"
@@ -141,12 +141,12 @@ class AssertTemplateUsedTests(TestCase):
         try:
             self.assertTemplateUsed(response, 'GET Template')
         except AssertionError, e:
-            self.assertEquals(str(e), "No templates used to render the response")
+            self.assertIn("No templates used to render the response", str(e))
 
         try:
             self.assertTemplateUsed(response, 'GET Template', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: No templates used to render the response")
+            self.assertIn("abc: No templates used to render the response", str(e))
 
     def test_single_context(self):
         "Template assertions work when there is a single context"
@@ -155,22 +155,22 @@ class AssertTemplateUsedTests(TestCase):
         try:
             self.assertTemplateNotUsed(response, 'Empty GET Template')
         except AssertionError, e:
-            self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response")
+            self.assertIn("Template 'Empty GET Template' was used unexpectedly in rendering the response", str(e))
 
         try:
             self.assertTemplateNotUsed(response, 'Empty GET Template', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Template 'Empty GET Template' was used unexpectedly in rendering the response")
+            self.assertIn("abc: Template 'Empty GET Template' was used unexpectedly in rendering the response", str(e))
 
         try:
             self.assertTemplateUsed(response, 'Empty POST Template')
         except AssertionError, e:
-            self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template")
+            self.assertIn("Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template", str(e))
 
         try:
             self.assertTemplateUsed(response, 'Empty POST Template', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template")
+            self.assertIn("abc: Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template", str(e))
 
     def test_multiple_context(self):
         "Template assertions work when there are multiple contexts"
@@ -186,17 +186,17 @@ class AssertTemplateUsedTests(TestCase):
         try:
             self.assertTemplateNotUsed(response, "form_view.html")
         except AssertionError, e:
-            self.assertEquals(str(e), "Template 'form_view.html' was used unexpectedly in rendering the response")
+            self.assertIn("Template 'form_view.html' was used unexpectedly in rendering the response", str(e))
 
         try:
             self.assertTemplateNotUsed(response, 'base.html')
         except AssertionError, e:
-            self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response")
+            self.assertIn("Template 'base.html' was used unexpectedly in rendering the response", str(e))
 
         try:
             self.assertTemplateUsed(response, "Valid POST Template")
         except AssertionError, e:
-            self.assertEquals(str(e), "Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html")
+            self.assertIn("Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html", str(e))
 
 class AssertRedirectsTests(TestCase):
     def test_redirect_page(self):
@@ -206,12 +206,12 @@ class AssertRedirectsTests(TestCase):
         try:
             self.assertRedirects(response, '/test_client/get_view/')
         except AssertionError, e:
-            self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)")
+            self.assertIn("Response didn't redirect as expected: Response code was 301 (expected 302)", str(e))
 
         try:
             self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 301 (expected 302)")
+            self.assertIn("abc: Response didn't redirect as expected: Response code was 301 (expected 302)", str(e))
 
     def test_lost_query(self):
         "An assertion is raised if the redirect location doesn't preserve GET parameters"
@@ -219,12 +219,12 @@ class AssertRedirectsTests(TestCase):
         try:
             self.assertRedirects(response, '/test_client/get_view/')
         except AssertionError, e:
-            self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'")
+            self.assertIn("Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'", str(e))
 
         try:
             self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'")
+            self.assertIn("abc: Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'", str(e))
 
     def test_incorrect_target(self):
         "An assertion is raised if the response redirects to another target"
@@ -233,7 +233,7 @@ class AssertRedirectsTests(TestCase):
             # Should redirect to get_view
             self.assertRedirects(response, '/test_client/some_view/')
         except AssertionError, e:
-            self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)")
+            self.assertIn("Response didn't redirect as expected: Response code was 301 (expected 302)", str(e))
 
     def test_target_page(self):
         "An assertion is raised if the response redirect target cannot be retrieved as expected"
@@ -242,13 +242,13 @@ class AssertRedirectsTests(TestCase):
             # The redirect target responds with a 301 code, not 200
             self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/')
         except AssertionError, e:
-            self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
+            self.assertIn("Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)", str(e))
 
         try:
             # The redirect target responds with a 301 code, not 200
             self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
+            self.assertIn("abc: Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)", str(e))
 
     def test_redirect_chain(self):
         "You can follow a redirect chain of multiple redirects"
@@ -339,12 +339,12 @@ class AssertRedirectsTests(TestCase):
         try:
             self.assertRedirects(response, '/test_client/get_view/')
         except AssertionError, e:
-            self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)")
+            self.assertIn("Response didn't redirect as expected: Response code was 200 (expected 302)", str(e))
 
         try:
             self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 200 (expected 302)")
+            self.assertIn("abc: Response didn't redirect as expected: Response code was 200 (expected 302)", str(e))
 
     def test_redirect_on_non_redirect_page(self):
         "An assertion is raised if the original page couldn't be retrieved as expected"
@@ -353,12 +353,12 @@ class AssertRedirectsTests(TestCase):
         try:
             self.assertRedirects(response, '/test_client/get_view/')
         except AssertionError, e:
-            self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)")
+            self.assertIn("Response didn't redirect as expected: Response code was 200 (expected 302)", str(e))
 
         try:
             self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 200 (expected 302)")
+            self.assertIn("abc: Response didn't redirect as expected: Response code was 200 (expected 302)", str(e))
 
 
 class AssertFormErrorTests(TestCase):
@@ -378,11 +378,11 @@ class AssertFormErrorTests(TestCase):
         try:
             self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.')
         except AssertionError, e:
-            self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response")
+            self.assertIn("The form 'wrong_form' was not used to render the response", str(e))
         try:
             self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEqual(str(e), "abc: The form 'wrong_form' was not used to render the response")
+            self.assertIn("abc: The form 'wrong_form' was not used to render the response", str(e))
 
     def test_unknown_field(self):
         "An assertion is raised if the field name is unknown"
@@ -400,11 +400,11 @@ class AssertFormErrorTests(TestCase):
         try:
             self.assertFormError(response, 'form', 'some_field', 'Some error.')
         except AssertionError, e:
-            self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'")
+            self.assertIn("The form 'form' in context 0 does not contain the field 'some_field'", str(e))
         try:
             self.assertFormError(response, 'form', 'some_field', 'Some error.', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEqual(str(e), "abc: The form 'form' in context 0 does not contain the field 'some_field'")
+            self.assertIn("abc: The form 'form' in context 0 does not contain the field 'some_field'", str(e))
 
     def test_noerror_field(self):
         "An assertion is raised if the field doesn't have any errors"
@@ -422,11 +422,11 @@ class AssertFormErrorTests(TestCase):
         try:
             self.assertFormError(response, 'form', 'value', 'Some error.')
         except AssertionError, e:
-            self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors")
+            self.assertIn("The field 'value' on form 'form' in context 0 contains no errors", str(e))
         try:
             self.assertFormError(response, 'form', 'value', 'Some error.', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEqual(str(e), "abc: The field 'value' on form 'form' in context 0 contains no errors")
+            self.assertIn("abc: The field 'value' on form 'form' in context 0 contains no errors", str(e))
 
     def test_unknown_error(self):
         "An assertion is raised if the field doesn't contain the provided error"
@@ -444,11 +444,11 @@ class AssertFormErrorTests(TestCase):
         try:
             self.assertFormError(response, 'form', 'email', 'Some error.')
         except AssertionError, e:
-            self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+            self.assertIn("The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])", str(e))
         try:
             self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEqual(str(e), "abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+            self.assertIn("abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])", str(e))
 
     def test_unknown_nonfield_error(self):
         """
@@ -469,11 +469,11 @@ class AssertFormErrorTests(TestCase):
         try:
             self.assertFormError(response, 'form', None, 'Some error.')
         except AssertionError, e:
-            self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
+            self.assertIn("The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e))
         try:
             self.assertFormError(response, 'form', None, 'Some error.', msg_prefix='abc')
         except AssertionError, e:
-            self.assertEqual(str(e), "abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
+            self.assertIn("abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e))
 
 class LoginTests(TestCase):
     fixtures = ['testdata']

+ 2 - 2
tests/regressiontests/test_runner/tests.py

@@ -2,9 +2,9 @@
 Tests for django test runner
 """
 import StringIO
-import unittest
-import django
+
 from django.test import simple
+from django.utils import unittest
 
 class DjangoTestRunnerTests(unittest.TestCase):
 

+ 1 - 2
tests/regressiontests/urlpatterns_reverse/tests.py

@@ -1,8 +1,6 @@
 """
 Unit tests for reverse URL lookups.
 """
-import unittest
-
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 from django.core.urlresolvers import reverse, resolve, NoReverseMatch,\
@@ -11,6 +9,7 @@ from django.core.urlresolvers import reverse, resolve, NoReverseMatch,\
 from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
 from django.shortcuts import redirect
 from django.test import TestCase
+from django.utils import unittest
 
 import urlconf_outer
 import urlconf_inner

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików