[test] get a chance to get proper garbage collection when running pytest on whole cw
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 03 Mar 2010 18:30:25 +0100
changeset 4766 162b2b127b15
parent 4764 ec9c20c6b9f7
child 4767 74b8e39d4825
[test] get a chance to get proper garbage collection when running pytest on whole cw
devtools/repotest.py
devtools/test/unittest_testlib.py
hooks/test/unittest_syncschema.py
pytestconf.py
server/__init__.py
server/test/unittest_checkintegrity.py
server/test/unittest_hook.py
server/test/unittest_ldapuser.py
server/test/unittest_msplanner.py
server/test/unittest_multisources.py
server/test/unittest_querier.py
server/test/unittest_repository.py
server/test/unittest_rql2sql.py
server/test/unittest_rqlannotation.py
server/test/unittest_schemaserial.py
server/test/unittest_ssplanner.py
--- a/devtools/repotest.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/devtools/repotest.py	Wed Mar 03 18:30:25 2010 +0100
@@ -95,6 +95,31 @@
     def __iter__(self):
         return iter(sorted(self.origdict, key=self.sortkey))
 
+def schema_eids_idx(schema):
+    """return a dictionary mapping schema types to their eids so we can reread
+    it from the fs instead of the db (too costly) between tests
+    """
+    schema_eids = {}
+    for x in schema.entities():
+        schema_eids[x] = x.eid
+    for x in schema.relations():
+        schema_eids[x] = x.eid
+        for rdef in x.rdefs.itervalues():
+            schema_eids[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid
+    return schema_eids
+
+def restore_schema_eids_idx(schema, schema_eids):
+    """rebuild schema eid index"""
+    for x in schema.entities():
+        x.eid = schema_eids[x]
+        schema._eid_index[x.eid] = x
+    for x in schema.relations():
+        x.eid = schema_eids[x]
+        schema._eid_index[x.eid] = x
+        for rdef in x.rdefs.itervalues():
+            rdef.eid = schema_eids[(rdef.subject, rdef.rtype, rdef.object)]
+            schema._eid_index[rdef.eid] = rdef
+
 
 from logilab.common.testlib import TestCase
 from rql import RQLHelper
--- a/devtools/test/unittest_testlib.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/devtools/test/unittest_testlib.py	Wed Mar 03 18:30:25 2010 +0100
@@ -15,6 +15,7 @@
 
 from cubicweb.devtools import htmlparser
 from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.pytestconf import clean_repo_test_cls
 
 class WebTestTC(TestCase):
 
@@ -37,7 +38,7 @@
         self.assertEquals(result.testsRun, 2)
         self.assertEquals(len(result.errors), 0)
         self.assertEquals(len(result.failures), 1)
-
+        clean_repo_test_cls(MyWebTest)
 
 
 HTML_PAGE = u"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
--- a/hooks/test/unittest_syncschema.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/hooks/test/unittest_syncschema.py	Wed Mar 03 18:30:25 2010 +0100
@@ -3,9 +3,11 @@
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.server.sqlutils import SQL_PREFIX
-
+from cubicweb.devtools.repotest import schema_eids_idx, restore_schema_eids_idx
 
-SCHEMA_EIDS = {}
+def teardown_module(*args):
+    del SchemaModificationHooksTC.schema_eids
+
 class SchemaModificationHooksTC(CubicWebTC):
     reset_schema = True
 
@@ -15,29 +17,12 @@
         # we have to read schema from the database to get eid for schema entities
         config._cubes = None
         cls.repo.fill_schema()
-        # remember them so we can reread it from the fs instead of the db (too
-        # costly) between tests
-        for x in cls.repo.schema.entities():
-            SCHEMA_EIDS[x] = x.eid
-        for x in cls.repo.schema.relations():
-            SCHEMA_EIDS[x] = x.eid
-            for rdef in x.rdefs.itervalues():
-                SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid
+        cls.schema_eids = schema_eids_idx(cls.repo.schema)
 
     @classmethod
     def _refresh_repo(cls):
         super(SchemaModificationHooksTC, cls)._refresh_repo()
-        # rebuild schema eid index
-        schema = cls.repo.schema
-        for x in schema.entities():
-            x.eid = SCHEMA_EIDS[x]
-            schema._eid_index[x.eid] = x
-        for x in cls.repo.schema.relations():
-            x.eid = SCHEMA_EIDS[x]
-            schema._eid_index[x.eid] = x
-            for rdef in x.rdefs.itervalues():
-                rdef.eid = SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)]
-                schema._eid_index[rdef.eid] = rdef
+        restore_schema_eids_idx(cls.repo.schema, cls.schema_eids)
 
     def index_exists(self, etype, attr, unique=False):
         self.session.set_pool()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytestconf.py	Wed Mar 03 18:30:25 2010 +0100
@@ -0,0 +1,29 @@
+"""pytest configuration file: we need this to properly remove ressources
+cached on test classes, at least until we've proper support for teardown_class
+"""
+import sys
+from os.path import split, splitext
+from logilab.common.pytest import PyTester
+
+from cubicweb.etwist.server import _gc_debug
+
+class CustomPyTester(PyTester):
+    def testfile(self, filename, batchmode=False):
+        try:
+            return super(CustomPyTester, self).testfile(filename, batchmode)
+        finally:
+            modname = splitext(split(filename)[1])[0]
+            for cls in vars(sys.modules[modname]).values():
+                if getattr(cls, '__module__', None) != modname:
+                    continue
+                clean_repo_test_cls(cls)
+            #_gc_debug()
+
+def clean_repo_test_cls(cls):
+    if 'repo' in cls.__dict__:
+        if cls.repo.__dict__: # empty dict when already shutted down
+            cls.repo.shutdown()
+        del cls.repo
+    for clsattr in ('cnx', '_orig_cnx', 'config', '_config', 'vreg', 'schema'):
+        if clsattr in cls.__dict__:
+            delattr(cls, clsattr)
--- a/server/__init__.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/__init__.py	Wed Mar 03 18:30:25 2010 +0100
@@ -200,6 +200,7 @@
     cnx.commit()
     cnx.close()
     session.close()
+    repo.shutdown()
     # restore initial configuration
     config.creating = False
     config.read_instance_schema = read_instance_schema
--- a/server/test/unittest_checkintegrity.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_checkintegrity.py	Wed Mar 03 18:30:25 2010 +0100
@@ -13,10 +13,9 @@
 
 from cubicweb.server.checkintegrity import check
 
-repo, cnx = init_test_database()
-
 class CheckIntegrityTC(TestCase):
     def test(self):
+        repo, cnx = init_test_database()
         sys.stderr = sys.stdout = StringIO()
         try:
             check(repo, cnx, ('entities', 'relations', 'text_index', 'metadata'),
@@ -24,6 +23,7 @@
         finally:
             sys.stderr = sys.__stderr__
             sys.stdout = sys.__stdout__
+        repo.shutdown()
 
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_hook.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_hook.py	Wed Mar 03 18:30:25 2010 +0100
@@ -69,6 +69,10 @@
 config.bootstrap_cubes()
 schema = config.load_schema()
 
+def teardown_module(*args):
+    global config, schema
+    del config, schema
+
 class AddAnyHook(hook.Hook):
     __regid__ = 'addany'
     category = 'cat1'
--- a/server/test/unittest_ldapuser.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_ldapuser.py	Wed Mar 03 18:30:25 2010 +0100
@@ -370,6 +370,11 @@
 LDAPUserSourceTC._init_repo()
 repo = LDAPUserSourceTC.repo
 
+def teardown_module(*args):
+    global repo
+    del repo
+    del RQL2LDAPFilterTC.schema
+
 class RQL2LDAPFilterTC(RQLGeneratorTC):
     schema = repo.schema
 
--- a/server/test/unittest_msplanner.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_msplanner.py	Wed Mar 03 18:30:25 2010 +0100
@@ -60,6 +60,11 @@
 # keep cnx so it's not garbage collected and the associated session is closed
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
+
 class BaseMSPlannerTC(BasePlannerTC):
     """test planner related feature on a 3-sources repository:
 
--- a/server/test/unittest_multisources.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_multisources.py	Wed Mar 03 18:30:25 2010 +0100
@@ -48,7 +48,12 @@
 def teardown_module(*args):
     PyroRQLSource.get_connection = PyroRQLSource_get_connection
     Connection.close = Connection_close
-
+    global repo2, cnx2, repo3, cnx3
+    repo2.shutdown()
+    repo3.shutdown()
+    del repo2, cnx2, repo3, cnx3
+    #del TwoSourcesTC.config.vreg
+    #del TwoSourcesTC.config
 
 class TwoSourcesTC(CubicWebTC):
     config = TwoSourcesConfiguration('data')
@@ -130,7 +135,7 @@
         cu = cnx.cursor()
         rset = cu.execute('Any X WHERE X has_text "card"')
         self.assertEquals(len(rset), 5, zip(rset.rows, rset.description))
-        cnx.close()
+        Connection_close(cnx)
 
     def test_synchronization(self):
         cu = cnx2.cursor()
--- a/server/test/unittest_querier.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_querier.py	Wed Mar 03 18:30:25 2010 +0100
@@ -48,6 +48,11 @@
 
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    cnx.close()
+    repo.shutdown()
+    del repo, cnx
 
 
 class UtilsTC(BaseQuerierTC):
--- a/server/test/unittest_repository.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_repository.py	Wed Mar 03 18:30:25 2010 +0100
@@ -21,7 +21,7 @@
 from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
                       UnknownEid, AuthenticationError)
 from cubicweb.schema import CubicWebSchema, RQLConstraint
-from cubicweb.dbapi import connect, repo_connect, multiple_connections_unfix
+from cubicweb.dbapi import connect, multiple_connections_unfix
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify
 from cubicweb.server import repository, hook
@@ -38,25 +38,29 @@
     """
 
     def test_fill_schema(self):
-        self.repo.schema = CubicWebSchema(self.repo.config.appid)
-        self.repo.config._cubes = None # avoid assertion error
-        self.repo.config.repairing = True # avoid versions checking
-        self.repo.fill_schema()
-        table = SQL_PREFIX + 'CWEType'
-        namecol = SQL_PREFIX + 'name'
-        finalcol = SQL_PREFIX + 'final'
-        self.session.set_pool()
-        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
-            namecol, table, finalcol))
-        self.assertEquals(cu.fetchall(), [])
-        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
-                          % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
-        self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
-                                          (u'Date',), (u'Datetime',),
-                                          (u'Decimal',),(u'Float',),
-                                          (u'Int',),
-                                          (u'Interval',), (u'Password',),
-                                          (u'String',), (u'Time',)])
+        origshema = self.repo.schema
+        try:
+            self.repo.schema = CubicWebSchema(self.repo.config.appid)
+            self.repo.config._cubes = None # avoid assertion error
+            self.repo.config.repairing = True # avoid versions checking
+            self.repo.fill_schema()
+            table = SQL_PREFIX + 'CWEType'
+            namecol = SQL_PREFIX + 'name'
+            finalcol = SQL_PREFIX + 'final'
+            self.session.set_pool()
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
+                namecol, table, finalcol))
+            self.assertEquals(cu.fetchall(), [])
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+                                         % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
+            self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
+                                              (u'Date',), (u'Datetime',),
+                                              (u'Decimal',),(u'Float',),
+                                              (u'Int',),
+                                              (u'Interval',), (u'Password',),
+                                              (u'String',), (u'Time',)])
+        finally:
+            self.repo.set_schema(origshema)
 
     def test_schema_has_owner(self):
         repo = self.repo
@@ -263,6 +267,8 @@
                 self.fail('something went wrong, thread still alive')
         finally:
             repository.pyro_unregister(self.repo.config)
+            from logilab.common import pyro_ext
+            pyro_ext._DAEMONS.clear()
 
     def _pyro_client(self, done):
         cnx = connect(self.repo.config.appid, u'admin', password='gingkow')
@@ -456,13 +462,6 @@
                            u'system.version.tag'])
 
 CALLED = []
-class EcritParHook(hook.Hook):
-    __regid__ = 'inlinedrelhook'
-    __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
-    events = ('before_add_relation', 'after_add_relation',
-              'before_delete_relation', 'after_delete_relation')
-    def __call__(self):
-        CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
 
 class InlineRelHooksTC(CubicWebTC):
     """test relation hooks are called for inlined relations
@@ -477,6 +476,14 @@
 
     def test_inline_relation(self):
         """make sure <event>_relation hooks are called for inlined relation"""
+        class EcritParHook(hook.Hook):
+            __regid__ = 'inlinedrelhook'
+            __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
+            events = ('before_add_relation', 'after_add_relation',
+                      'before_delete_relation', 'after_delete_relation')
+            def __call__(self):
+                CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
+
         self.hm.register(EcritParHook)
         eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
         eidn = self.execute('INSERT Note X: X type "T"')[0][0]
--- a/server/test/unittest_rql2sql.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_rql2sql.py	Wed Mar 03 18:30:25 2010 +0100
@@ -37,6 +37,10 @@
 schema['state_of'].inlined = False
 schema['comments'].inlined = False
 
+def teardown_module(*args):
+    global config, schema
+    del config, schema
+
 PARSER = [
     (r"Personne P WHERE P nom 'Zig\'oto';",
      '''SELECT _P.cw_eid
--- a/server/test/unittest_rqlannotation.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_rqlannotation.py	Wed Mar 03 18:30:25 2010 +0100
@@ -8,6 +8,11 @@
 
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
+
 class SQLGenAnnotatorTC(BaseQuerierTC):
     repo = repo
 
--- a/server/test/unittest_schemaserial.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_schemaserial.py	Wed Mar 03 18:30:25 2010 +0100
@@ -15,6 +15,10 @@
 config.bootstrap_cubes()
 schema = loader.load(config)
 
+def teardown_module(*args):
+    global schema, config, loader
+    del schema, config, loader
+
 from cubicweb.server.schemaserial import *
 from cubicweb.server.schemaserial import _erperms2rql as erperms2rql
 
--- a/server/test/unittest_ssplanner.py	Wed Mar 03 17:59:05 2010 +0100
+++ b/server/test/unittest_ssplanner.py	Wed Mar 03 18:30:25 2010 +0100
@@ -12,6 +12,10 @@
 # keep cnx so it's not garbage collected and the associated session closed
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
 class SSPlannerTC(BasePlannerTC):
     repo = repo
     _test = test_plan