Mercurial > p > roundup > code
diff test/test_postgresql.py @ 7719:3071db43bfb6
feat: issue2550852 - support using a specified PostgreSQL db schema
Finally after 7 years this is closed.
roundup/backends/back_postgresql.py:
Support use of schema when specified in RDBMS_NAME. Stuart McGraws
code is finally merged 8-).
test/test_postgresql.py, test/conftest.py:
Run all postgresql tests in the schema db as well.
Also make sure that db_nuke raises an error when trying to delete
the schema test database. Conftest defines pg_schema mark that can
be used to exclude schema tests with pytest -m "not pg_schema".
roundup/configuration.py:
change doc on RDBMS_NAME to include db.schema form.
.travis.yml, .github/workflows/ci-test.yml:
create schema test db; add user for testing with schema; grant new
user create privs for schema.
doc/installation.txt:
Reference to roundup-admin init deleting schema added.
doc/mysql.txt doc/postgresql.txt:
New documentation on psql/mysql commands to set up a production db.
doc/upgrading.txt:
mention schema support, also document service setting for
selecting connection from pg_service.conf.
doc/reference.txt:
update config.ini documentation for RDBMS_NAME.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Wed, 27 Dec 2023 22:52:14 -0500 |
| parents | 521d98231e5c |
| children | 8147f6deac9f |
line wrap: on
line diff
--- a/test/test_postgresql.py Sat Dec 23 21:59:08 2023 -0500 +++ b/test/test_postgresql.py Wed Dec 27 22:52:14 2023 -0500 @@ -20,6 +20,7 @@ import pytest from roundup.hyperdb import DatabaseError from roundup.backends import get_backend, have_backend +from roundup.backends.back_postgresql import db_command, db_schema_split from .db_test_base import DBTest, ROTest, config, SchemaTest, ClassicInitTest from .db_test_base import ConcurrentDBTest, HTMLItemTest, FilterCacheTest @@ -52,10 +53,15 @@ # where an aborted test run (^C during setUp for example) # leaves the database in an unusable, partly configured state. try: - cls.nuke_database(cls) - except: - # ignore failure to nuke the database. - pass + cls.nuke_database() + except Exception as m: + # ignore failure to nuke the database if it doesn't exist. + # otherwise abort + if str(m.args[0]) == ( + 'database "%s" does not exist' % config.RDBMS_NAME): + pass + else: + raise def setUp(self): pass @@ -63,32 +69,95 @@ def tearDown(self): self.nuke_database() + @classmethod def nuke_database(self): # clear out the database - easiest way is to nuke and re-create it self.module.db_nuke(config) +@skip_postgresql +class postgresqlSchemaOpener: + + RDBMS_NAME="rounduptest_schema.rounduptest" + RDBMS_USER="rounduptest_schema" + + if have_backend('postgresql'): + module = get_backend('postgresql') + + def setup_class(cls): + # nuke the schema for the class. Handles the case + # where an aborted test run (^C during setUp for example) + # leaves the database in an unusable, partly configured state. + config.RDBMS_NAME=cls.RDBMS_NAME + config.RDBMS_USER=cls.RDBMS_USER + + database, schema = db_schema_split(config.RDBMS_NAME) + + try: + cls.nuke_database() + except Exception as m: + # ignore failure to nuke the database if it doesn't exist. + # otherwise abort + if str(m.args[0]) == ( + 'schema "%s" does not exist' % schema): + pass + else: + raise + + def setUp(self): + # make sure to override the rdbms settings. + # before every test. + config.RDBMS_NAME=self.RDBMS_NAME + config.RDBMS_USER=self.RDBMS_USER + + def tearDown(self): + self.nuke_database() + config.RDBMS_NAME="rounduptest" + config.RDBMS_USER="rounduptest" + + @classmethod + def nuke_database(self): + # clear out the database - easiest way is to nuke and re-create it + self.module.db_nuke(config) @skip_postgresql -class postgresqlDBTest(postgresqlOpener, DBTest, unittest.TestCase): +class postgresqlPrecreatedSchemaDbOpener: + """Open the db where the user has only schema create rights. + The test tries to nuke the db and should result in an exception. + + RDBMS_NAME should not have the .schema on it as we want to + operate on the db itself with db_nuke. + """ + + RDBMS_NAME="rounduptest_schema" + RDBMS_USER="rounduptest_schema" + + if have_backend('postgresql'): + module = get_backend('postgresql') + + def setup_class(cls): + # nuke the schema for the class. Handles the case + # where an aborted test run (^C during setUp for example) + # leaves the database in an unusable, partly configured state. + config.RDBMS_NAME=cls.RDBMS_NAME + config.RDBMS_USER=cls.RDBMS_USER + def setUp(self): - # set for manual integration testing of 'native-fts' - # It is unset in tearDown so it doesn't leak into other tests. - # FIXME extract test methods in DBTest that hit the indexer - # into a new class (DBTestIndexer). Add DBTestIndexer - # to this class. - # Then create a new class in this file: - # postgresqlDBTestIndexerNative_FTS - # that imports from DBestIndexer to test native-fts. - # config['INDEXER'] = 'native-fts' - postgresqlOpener.setUp(self) - DBTest.setUp(self) + # make sure to override the rdbms settings. + # before every test. + config.RDBMS_NAME=self.RDBMS_NAME + config.RDBMS_USER=self.RDBMS_USER def tearDown(self): - # clean up config to prevent leak if native-fts is tested - config['INDEXER'] = '' - DBTest.tearDown(self) - postgresqlOpener.tearDown(self) + config.RDBMS_NAME="rounduptest" + config.RDBMS_USER="rounduptest" + @classmethod + def nuke_database(self): + # clear out the database - easiest way is to nuke and re-create it + self.module.db_nuke(config) + +@skip_postgresql +class postgresqlAdditionalDBTest(): def testUpgrade_6_to_7(self): # load the database @@ -225,6 +294,52 @@ self.assertEqual(self.db.database_schema['version'], 8) @skip_postgresql +class postgresqlDBTest(postgresqlOpener, DBTest, + postgresqlAdditionalDBTest, unittest.TestCase): + def setUp(self): + # set for manual integration testing of 'native-fts' + # It is unset in tearDown so it doesn't leak into other tests. + # FIXME extract test methods in DBTest that hit the indexer + # into a new class (DBTestIndexer). Add DBTestIndexer + # to this class. + # Then create a new class in this file: + # postgresqlDBTestIndexerNative_FTS + # that imports from DBestIndexer to test native-fts. + # config['INDEXER'] = 'native-fts' + postgresqlOpener.setUp(self) + DBTest.setUp(self) + + def tearDown(self): + # clean up config to prevent leak if native-fts is tested + config['INDEXER'] = '' + DBTest.tearDown(self) + postgresqlOpener.tearDown(self) + +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlDBTestSchema(postgresqlSchemaOpener, DBTest, + postgresqlAdditionalDBTest, unittest.TestCase): + def setUp(self): + # set for manual integration testing of 'native-fts' + # It is unset in tearDown so it doesn't leak into other tests. + # FIXME extract test methods in DBTest that hit the indexer + # into a new class (DBTestIndexer). Add DBTestIndexer + # to this class. + # Then create a new class in this file: + # postgresqlDBTestIndexerNative_FTS + # that imports from DBestIndexer to test native-fts. + # config['INDEXER'] = 'native-fts' + postgresqlSchemaOpener.setUp(self) + DBTest.setUp(self) + + def tearDown(self): + # clean up config to prevent leak if native-fts is tested + config['INDEXER'] = '' + DBTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + + +@skip_postgresql class postgresqlROTest(postgresqlOpener, ROTest, unittest.TestCase): def setUp(self): postgresqlOpener.setUp(self) @@ -234,6 +349,18 @@ ROTest.tearDown(self) postgresqlOpener.tearDown(self) +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlROTestSchema(postgresqlSchemaOpener, ROTest, + unittest.TestCase): + def setUp(self): + postgresqlSchemaOpener.setUp(self) + ROTest.setUp(self) + + def tearDown(self): + ROTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + @skip_postgresql class postgresqlConcurrencyTest(postgresqlOpener, ConcurrentDBTest, @@ -247,29 +374,23 @@ ConcurrentDBTest.tearDown(self) postgresqlOpener.tearDown(self) - @skip_postgresql -class postgresqlJournalTest(postgresqlOpener, ClassicInitBase, - unittest.TestCase): +@pytest.mark.pg_schema +class postgresqlConcurrencyTestSchema(postgresqlSchemaOpener, ConcurrentDBTest, + unittest.TestCase): backend = 'postgresql' def setUp(self): - postgresqlOpener.setUp(self) - ClassicInitBase.setUp(self) - self.tracker = setupTracker(self.dirname, self.backend) - db = self.tracker.open('admin') - self.id = db.issue.create(title='initial value') - db.commit() - db.close() + postgresqlSchemaOpener.setUp(self) + ConcurrentDBTest.setUp(self) def tearDown(self): - try: - self.db1.close() - self.db2.close() - except psycopg2.InterfaceError as exc: - if 'connection already closed' in str(exc): pass - else: raise - ClassicInitBase.tearDown(self) - postgresqlOpener.tearDown(self) + ConcurrentDBTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + + + +@skip_postgresql +class postgresqlAdditionalJournalTest(): def _test_journal(self, expected_journal): id = self.id @@ -308,6 +429,56 @@ exc = self.module.TransactionRollbackError self.assertRaises(exc, self._test_journal, []) +@skip_postgresql +class postgresqlJournalTest(postgresqlOpener, ClassicInitBase, + postgresqlAdditionalJournalTest, + unittest.TestCase): + backend = 'postgresql' + def setUp(self): + postgresqlOpener.setUp(self) + ClassicInitBase.setUp(self) + self.tracker = setupTracker(self.dirname, self.backend) + db = self.tracker.open('admin') + self.id = db.issue.create(title='initial value') + db.commit() + db.close() + + def tearDown(self): + try: + self.db1.close() + self.db2.close() + except psycopg2.InterfaceError as exc: + if 'connection already closed' in str(exc): pass + else: raise + ClassicInitBase.tearDown(self) + postgresqlOpener.tearDown(self) + + +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlJournalTestSchema(postgresqlSchemaOpener, ClassicInitBase, + postgresqlAdditionalJournalTest, + unittest.TestCase): + backend = 'postgresql' + def setUp(self): + postgresqlSchemaOpener.setUp(self) + ClassicInitBase.setUp(self) + self.tracker = setupTracker(self.dirname, self.backend) + db = self.tracker.open('admin') + self.id = db.issue.create(title='initial value') + db.commit() + db.close() + + def tearDown(self): + try: + self.db1.close() + self.db2.close() + except psycopg2.InterfaceError as exc: + if 'connection already closed' in str(exc): pass + else: raise + ClassicInitBase.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + @skip_postgresql class postgresqlHTMLItemTest(postgresqlOpener, HTMLItemTest, @@ -323,6 +494,20 @@ @skip_postgresql +@pytest.mark.pg_schema +class postgresqlHTMLItemTestSchema(postgresqlSchemaOpener, HTMLItemTest, + unittest.TestCase): + backend = 'postgresql' + def setUp(self): + postgresqlSchemaOpener.setUp(self) + HTMLItemTest.setUp(self) + + def tearDown(self): + HTMLItemTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + + +@skip_postgresql class postgresqlFilterCacheTest(postgresqlOpener, FilterCacheTest, unittest.TestCase): backend = 'postgresql' @@ -334,6 +519,19 @@ FilterCacheTest.tearDown(self) postgresqlOpener.tearDown(self) +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlFilterCacheTestSchema(postgresqlSchemaOpener, FilterCacheTest, + unittest.TestCase): + backend = 'postgresql' + def setUp(self): + postgresqlSchemaOpener.setUp(self) + FilterCacheTest.setUp(self) + + def tearDown(self): + FilterCacheTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + @skip_postgresql class postgresqlSchemaTest(postgresqlOpener, SchemaTest, unittest.TestCase): @@ -347,6 +545,19 @@ @skip_postgresql +@pytest.mark.pg_schema +class postgresqlSchemaTestSchema(postgresqlSchemaOpener, SchemaTest, + unittest.TestCase): + def setUp(self): + postgresqlSchemaOpener.setUp(self) + SchemaTest.setUp(self) + + def tearDown(self): + SchemaTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + + +@skip_postgresql class postgresqlClassicInitTest(postgresqlOpener, ClassicInitTest, unittest.TestCase): backend = 'postgresql' @@ -359,6 +570,20 @@ postgresqlOpener.tearDown(self) +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlClassicInitTestSchema(postgresqlSchemaOpener, ClassicInitTest, + unittest.TestCase): + backend = 'postgresql' + def setUp(self): + postgresqlSchemaOpener.setUp(self) + ClassicInitTest.setUp(self) + + def tearDown(self): + ClassicInitTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + + from .session_common import SessionTest @skip_postgresql class postgresqlSessionTest(postgresqlOpener, SessionTest, unittest.TestCase): @@ -371,6 +596,21 @@ SessionTest.tearDown(self) postgresqlOpener.tearDown(self) + +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlSessionTestSchema(postgresqlSchemaOpener, SessionTest, + unittest.TestCase): + s2b = lambda x,y : y + + def setUp(self): + postgresqlSchemaOpener.setUp(self) + SessionTest.setUp(self) + def tearDown(self): + SessionTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + + @skip_postgresql class postgresqlSpecialActionTestCase(postgresqlOpener, SpecialActionTest, unittest.TestCase): @@ -384,7 +624,60 @@ postgresqlOpener.tearDown(self) @skip_postgresql -class postgresqlRestTest (RestTestCase, unittest.TestCase): +@pytest.mark.pg_schema +class postgresqlSpecialActionTestCaseSchema(postgresqlSchemaOpener, + SpecialActionTest, + unittest.TestCase): + backend = 'postgresql' + def setUp(self): + postgresqlSchemaOpener.setUp(self) + SpecialActionTest.setUp(self) + + def tearDown(self): + SpecialActionTest.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + +@skip_postgresql +class postgresqlRestTest (postgresqlOpener, RestTestCase, unittest.TestCase): backend = 'postgresql' + def setUp(self): + postgresqlOpener.setUp(self) + RestTestCase.setUp(self) + + def tearDown(self): + RestTestCase.tearDown(self) + postgresqlOpener.tearDown(self) + + +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlRestTestSchema(postgresqlSchemaOpener, RestTestCase, + unittest.TestCase): + backend = 'postgresql' + def setUp(self): + postgresqlSchemaOpener.setUp(self) + RestTestCase.setUp(self) + + def tearDown(self): + RestTestCase.tearDown(self) + postgresqlSchemaOpener.tearDown(self) + + +@skip_postgresql +@pytest.mark.pg_schema +class postgresqlDbDropFailureTestSchema(postgresqlPrecreatedSchemaDbOpener, + unittest.TestCase): + + def test_drop(self): + """Verify that the schema test database can not be dropped.""" + + with self.assertRaises(RuntimeError) as m: + self.module.db_nuke(config) + + + self.assertEqual(m.exception.args[0], + 'must be owner of database rounduptest_schema') + + # vim: set et sts=4 sw=4 :
