class DatabaseManagerTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.dbm = DatabaseManager(self.env) def tearDown(self): self.env.reset_db() def test_destroy_db(self): """Database doesn't exist after calling destroy_db.""" self.env.db_query("SELECT name FROM system") self.assertIsNotNone(self.dbm._cnx_pool) self.dbm.destroy_db() self.assertIsNone(self.dbm._cnx_pool) # No connection pool self.assertFalse(self.dbm.db_exists()) def test_get_column_names(self): """Get column names for the default database.""" for table in default_schema: column_names = [col.name for col in table.columns] self.assertEqual(column_names, self.dbm.get_column_names(table.name)) def test_get_default_database_version(self): """Get database version for the default entry named `database_version`. """ self.assertEqual(default_db_version, self.dbm.get_database_version()) def test_get_table_names(self): """Get table names for the default database.""" self.assertEqual(sorted(table.name for table in default_schema), sorted(self.dbm.get_table_names())) def test_set_default_database_version(self): """Set database version for the default entry named `database_version`. """ new_db_version = default_db_version + 1 self.dbm.set_database_version(new_db_version) self.assertEqual(new_db_version, self.dbm.get_database_version()) # Restore the previous version to avoid destroying the database # on teardown self.dbm.set_database_version(default_db_version) self.assertEqual(default_db_version, self.dbm.get_database_version()) def test_set_get_plugin_database_version(self): """Get and set database version for an entry with an arbitrary name. """ name = 'a_trac_plugin_version' db_ver = 1 self.assertFalse(self.dbm.get_database_version(name)) self.dbm.set_database_version(db_ver, name) self.assertEqual(db_ver, self.dbm.get_database_version(name))
class EnvironmentStub(Environment): """A stub of the trac.env.Environment class for testing.""" global_databasemanager = None required = False abstract = True def __init__(self, default_data=False, enable=None, disable=None, path=None, destroying=False): """Construct a new Environment stub object. :param default_data: If True, populate the database with some defaults. :param enable: A list of component classes or name globs to activate in the stub environment. :param disable: A list of component classes or name globs to deactivate in the stub environment. :param path: The location of the environment in the file system. No files or directories are created when specifying this parameter. :param destroying: If True, the database will not be reset. This is useful for cases when the object is being constructed in order to call `destroy_db`. """ if enable is not None and not isinstance(enable, (list, tuple)): raise TypeError('Keyword argument "enable" must be a list') if disable is not None and not isinstance(disable, (list, tuple)): raise TypeError('Keyword argument "disable" must be a list') ComponentManager.__init__(self) self.systeminfo = [] import trac self.path = path if self.path is None: self.path = os.path.dirname(trac.__file__) if not os.path.isabs(self.path): self.path = os.path.join(os.getcwd(), self.path) # -- configuration self.config = Configuration(None) # We have to have a ticket-workflow config for ''lots'' of things to # work. So insert the basic-workflow config here. There may be a # better solution than this. load_workflow_config_snippet(self.config, 'basic-workflow.ini') self.config.set('logging', 'log_level', 'DEBUG') self.config.set('logging', 'log_type', 'stderr') if enable is not None: self.config.set('components', 'trac.*', 'disabled') else: self.config.set('components', 'tracopt.versioncontrol.*', 'enabled') for name_or_class in enable or (): config_key = self._component_name(name_or_class) self.config.set('components', config_key, 'enabled') for name_or_class in disable or (): config_key = self._component_name(name_or_class) self.config.set('components', config_key, 'disabled') # -- logging from trac.log import logger_handler_factory self.log, self._log_handler = logger_handler_factory('test') # -- database self.config.set('components', 'trac.db.*', 'enabled') self.dburi = get_dburi() init_global = False if self.global_databasemanager: self.components[DatabaseManager] = self.global_databasemanager else: self.config.set('trac', 'database', self.dburi) self.global_databasemanager = DatabaseManager(self) self.config.set('trac', 'debug_sql', True) init_global = not destroying if default_data or init_global: self.reset_db(default_data) self.config.set('trac', 'base_url', 'http://example.org/trac.cgi') translation.activate(locale_en) def reset_db(self, default_data=None): """Remove all data from Trac tables, keeping the tables themselves. :param default_data: after clean-up, initialize with default data :return: True upon success """ from trac import db_default tables = [] dbm = self.global_databasemanager try: with self.db_transaction as db: db.rollback() # make sure there's no transaction in progress # check the database version db_version = dbm.get_database_version() except (TracError, self.env.db_exc.DatabaseError): pass else: if db_version == db_default.db_version: # same version, simply clear the tables (faster) tables = dbm.reset_tables() else: # different version or version unknown, drop the tables self.destroy_db() if not tables: dbm.init_db() # we need to make sure the next get_db_cnx() will re-create # a new connection aware of the new data model - see #8518. if self.dburi != 'sqlite::memory:': dbm.shutdown() if default_data: dbm.insert_into_tables(db_default.get_data) else: dbm.set_database_version(db_default.db_version) def destroy_db(self, scheme=None, db_prop=None): """Destroy the database. :since 1.1.5: the `scheme` and `db_prop` parameters are deprecated and will be removed in 1.3.1. """ try: self.global_databasemanager.destroy_db() except (TracError, self.db_exc.DatabaseError): pass return False def insert_known_users(self, users): with self.env.db_transaction as db: for username, name, email in users: db("INSERT INTO session VALUES (%s, %s, %s)", (username, 1, int(time.time()))) db("INSERT INTO session_attribute VALUES (%s,%s,'name',%s)", (username, 1, name)) db("INSERT INTO session_attribute VALUES (%s,%s,'email',%s)", (username, 1, email)) # overridden def is_component_enabled(self, cls): if self._component_name(cls).startswith('__main__.'): return True return Environment.is_component_enabled(self, cls)
class DatabaseManagerTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.dbm = DatabaseManager(self.env) def tearDown(self): self.env.reset_db() def test_destroy_db(self): """Database doesn't exist after calling destroy_db.""" with self.env.db_query as db: db("SELECT name FROM " + db.quote('system')) self.assertIsNotNone(self.dbm._cnx_pool) self.dbm.destroy_db() self.assertIsNone(self.dbm._cnx_pool) # No connection pool scheme, params = parse_connection_uri(get_dburi()) if scheme != 'postgres' or params.get('schema', 'public') != 'public': self.assertFalse(self.dbm.db_exists()) else: self.assertEqual([], self.dbm.get_table_names()) def test_get_column_names(self): """Get column names for the default database.""" for table in default_schema: column_names = [col.name for col in table.columns] self.assertEqual(column_names, self.dbm.get_column_names(table.name)) def test_get_default_database_version(self): """Get database version for the default entry named `database_version`. """ self.assertEqual(default_db_version, self.dbm.get_database_version()) def test_get_table_names(self): """Get table names for the default database.""" self.assertEqual(sorted(table.name for table in default_schema), sorted(self.dbm.get_table_names())) def test_has_table(self): self.assertIs(True, self.dbm.has_table('system')) self.assertIs(True, self.dbm.has_table('wiki')) self.assertIs(False, self.dbm.has_table('trac')) self.assertIs(False, self.dbm.has_table('blah.blah')) def test_no_database_version(self): """False is returned when entry doesn't exist""" self.assertFalse(self.dbm.get_database_version('trac_plugin_version')) def test_set_default_database_version(self): """Set database version for the default entry named `database_version`. """ new_db_version = default_db_version + 1 self.dbm.set_database_version(new_db_version) self.assertEqual(new_db_version, self.dbm.get_database_version()) self.assertEqual([('INFO', 'Upgraded database_version from 45 to 46')], self.env.log_messages) # Restore the previous version to avoid destroying the database # on teardown self.dbm.set_database_version(default_db_version) self.assertEqual(default_db_version, self.dbm.get_database_version()) def test_set_get_plugin_database_version(self): """Get and set database version for an entry with an arbitrary name. """ name = 'trac_plugin_version' db_ver = 1 self.dbm.set_database_version(db_ver, name) self.assertEqual([], self.env.log_messages) self.assertEqual(db_ver, self.dbm.get_database_version(name)) # DB update will be skipped when new value equals database version self.dbm.set_database_version(db_ver, name) self.assertEqual([], self.env.log_messages) def test_get_sequence_names(self): sequence_names = [] if self.dbm.connection_uri.startswith('postgres'): for table in default_schema: for column in table.columns: if column.name == 'id' and column.auto_increment: sequence_names.append(table.name) sequence_names.sort() self.assertEqual(sequence_names, self.dbm.get_sequence_names())
class EnvironmentStub(Environment): """A stub of the trac.env.Environment class for testing.""" global_databasemanager = None required = False def __init__(self, default_data=False, enable=None, disable=None, path=None, destroying=False): """Construct a new Environment stub object. :param default_data: If True, populate the database with some defaults. :param enable: A list of component classes or name globs to activate in the stub environment. :param disable: A list of component classes or name globs to deactivate in the stub environment. :param path: The location of the environment in the file system. No files or directories are created when specifying this parameter. :param destroying: If True, the database will not be reset. This is useful for cases when the object is being constructed in order to call `destroy_db`. """ if enable is not None and not isinstance(enable, (list, tuple)): raise TypeError('Keyword argument "enable" must be a list') if disable is not None and not isinstance(disable, (list, tuple)): raise TypeError('Keyword argument "disable" must be a list') ComponentManager.__init__(self) self.systeminfo = [] import trac self.path = path if self.path is None: self.path = os.path.dirname(trac.__file__) if not os.path.isabs(self.path): self.path = os.path.join(os.getcwd(), self.path) # -- configuration self.config = ConfigurationStub(None) # We have to have a ticket-workflow config for ''lots'' of things to # work. So insert the basic-workflow config here. There may be a # better solution than this. load_workflow_config_snippet(self.config, 'basic-workflow.ini') self.config.set('logging', 'log_level', 'DEBUG') self.config.set('logging', 'log_type', 'stderr') if enable is not None: self.config.set('components', 'trac.*', 'disabled') else: self.config.set('components', 'tracopt.versioncontrol.*', 'enabled') for name_or_class in enable or (): config_key = self._component_name(name_or_class) self.config.set('components', config_key, 'enabled') for name_or_class in disable or (): config_key = self._component_name(name_or_class) self.config.set('components', config_key, 'disabled') # -- logging from trac.log import logger_handler_factory self.log, self._log_handler = logger_handler_factory('test') # -- database self.config.set('components', 'trac.db.*', 'enabled') self.dburi = get_dburi() init_global = False if self.global_databasemanager: self.components[DatabaseManager] = self.global_databasemanager else: self.config.set('trac', 'database', self.dburi) self.global_databasemanager = DatabaseManager(self) self.config.set('trac', 'debug_sql', True) init_global = not destroying if default_data or init_global: self.reset_db(default_data) self.config.set('trac', 'base_url', 'http://example.org/trac.cgi') translation.activate(locale_en) def reset_db(self, default_data=None): """Remove all data from Trac tables, keeping the tables themselves. :param default_data: after clean-up, initialize with default data :return: True upon success """ from trac import db_default tables = [] dbm = self.global_databasemanager try: with self.db_transaction as db: db.rollback() # make sure there's no transaction in progress # check the database version db_version = dbm.get_database_version() except (TracError, self.env.db_exc.DatabaseError): pass else: if db_version == db_default.db_version: # same version, simply clear the tables (faster) tables = dbm.reset_tables() else: # different version or version unknown, drop the tables self.destroy_db() if not tables: dbm.init_db() # we need to make sure the next get_db_cnx() will re-create # a new connection aware of the new data model - see #8518. if self.dburi != 'sqlite::memory:': dbm.shutdown() if default_data: dbm.insert_into_tables(db_default.get_data) else: dbm.set_database_version(db_default.db_version) def destroy_db(self, scheme=None, db_prop=None): """Destroy the database. :since 1.1.5: the `scheme` and `db_prop` parameters are deprecated and will be removed in 1.3.1. """ try: self.global_databasemanager.destroy_db() except (TracError, self.db_exc.DatabaseError): pass return False def insert_known_users(self, users): with self.env.db_transaction as db: for username, name, email in users: db("INSERT INTO session VALUES (%s, %s, %s)", (username, 1, int(time.time()))) db("INSERT INTO session_attribute VALUES (%s,%s,'name',%s)", (username, 1, name)) db("INSERT INTO session_attribute VALUES (%s,%s,'email',%s)", (username, 1, email)) # overridden def is_component_enabled(self, cls): if self._component_name(cls).startswith('__main__.'): return True return Environment.is_component_enabled(self, cls)