Browse Source

Refactor tests to use Alembic to run migrations

* Functional tests now use alembic instead of sqlalchmey-migrate
  to build and destroy test database.
* All tests now use a file-based sqlite db as opposed to an in-memory
  database.

Partially-Implements: blueprint alembic-migrations
Change-Id: I77921366a05ba6f9841143af89c1f4059d8454c6
Depends-On: Ie8594ff339a13bf190aefa308f54e97ee20ecfa2
tags/14.0.0.0rc1
Hemanth Makkapati 2 years ago
parent
commit
95c7c1b753

+ 0
- 13
glance/db/migration.py View File

@@ -26,8 +26,6 @@ from oslo_config import cfg
26 26
 from oslo_db import options as db_options
27 27
 from stevedore import driver
28 28
 
29
-from glance.db.sqlalchemy import api as db_api
30
-
31 29
 
32 30
 _IMPL = None
33 31
 _LOCK = threading.Lock()
@@ -53,14 +51,3 @@ MIGRATE_REPO_PATH = os.path.join(
53 51
     'sqlalchemy',
54 52
     'migrate_repo',
55 53
 )
56
-
57
-
58
-def db_sync(version=None, init_version=0, engine=None):
59
-    """Migrate the database to `version` or the most recent version."""
60
-
61
-    if engine is None:
62
-        engine = db_api.get_engine()
63
-    return get_backend().db_sync(engine=engine,
64
-                                 abs_path=MIGRATE_REPO_PATH,
65
-                                 version=version,
66
-                                 init_version=init_version)

+ 8
- 7
glance/db/sqlalchemy/alembic_migrations/__init__.py View File

@@ -20,19 +20,20 @@ from alembic import command as alembic_command
20 20
 from alembic import config as alembic_config
21 21
 from alembic import migration as alembic_migration
22 22
 from oslo_db import exception as db_exception
23
-from oslo_db.sqlalchemy import migration
23
+from oslo_db.sqlalchemy import migration as sqla_migration
24 24
 
25 25
 from glance.db import migration as db_migration
26 26
 from glance.db.sqlalchemy import api as db_api
27 27
 from glance.i18n import _
28 28
 
29 29
 
30
-def get_alembic_config():
30
+def get_alembic_config(engine=None):
31 31
     """Return a valid alembic config object"""
32 32
     ini_path = os.path.join(os.path.dirname(__file__), 'alembic.ini')
33 33
     config = alembic_config.Config(os.path.abspath(ini_path))
34
-    dbconn = str(db_api.get_engine().url)
35
-    config.set_main_option('sqlalchemy.url', dbconn)
34
+    if engine is None:
35
+        engine = db_api.get_engine()
36
+    config.set_main_option('sqlalchemy.url', str(engine.url))
36 37
     return config
37 38
 
38 39
 
@@ -47,9 +48,9 @@ def get_current_alembic_heads():
47 48
 
48 49
 def get_current_legacy_head():
49 50
     try:
50
-        legacy_head = migration.db_version(db_api.get_engine(),
51
-                                           db_migration.MIGRATE_REPO_PATH,
52
-                                           db_migration.INIT_VERSION)
51
+        legacy_head = sqla_migration.db_version(db_api.get_engine(),
52
+                                                db_migration.MIGRATE_REPO_PATH,
53
+                                                db_migration.INIT_VERSION)
53 54
     except db_exception.DbMigrationError:
54 55
         legacy_head = None
55 56
     return legacy_head

+ 0
- 0
glance/db/sqlalchemy/alembic_migrations/versions/__init__.py View File


+ 0
- 0
glance/tests/functional/db/migrations/__init__.py View File


+ 48
- 0
glance/tests/functional/db/migrations/test_mitaka01.py View File

@@ -0,0 +1,48 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from oslo_db.sqlalchemy import test_base
14
+import sqlalchemy
15
+
16
+from glance.tests.functional.db import test_migrations
17
+
18
+
19
+def get_indexes(table, engine):
20
+    inspector = sqlalchemy.inspect(engine)
21
+    return [idx['name'] for idx in inspector.get_indexes(table)]
22
+
23
+
24
+class TestMitaka01Mixin(test_migrations.AlembicMigrationsMixin):
25
+
26
+    def _pre_upgrade_mitaka01(self, engine):
27
+        indexes = get_indexes('images', engine)
28
+        self.assertNotIn('created_at_image_idx', indexes)
29
+        self.assertNotIn('updated_at_image_idx', indexes)
30
+
31
+    def _check_mitaka01(self, engine, data):
32
+        indexes = get_indexes('images', engine)
33
+        self.assertIn('created_at_image_idx', indexes)
34
+        self.assertIn('updated_at_image_idx', indexes)
35
+
36
+
37
+class TestMitaka01MySQL(TestMitaka01Mixin,
38
+                        test_base.MySQLOpportunisticTestCase):
39
+    pass
40
+
41
+
42
+class TestMitaka01PostgresSQL(TestMitaka01Mixin,
43
+                              test_base.PostgreSQLOpportunisticTestCase):
44
+    pass
45
+
46
+
47
+class TestMitaka01Sqlite(TestMitaka01Mixin, test_base.DbTestCase):
48
+    pass

+ 65
- 0
glance/tests/functional/db/migrations/test_mitaka02.py View File

@@ -0,0 +1,65 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import datetime
14
+
15
+from oslo_db.sqlalchemy import test_base
16
+from oslo_db.sqlalchemy import utils as db_utils
17
+
18
+from glance.tests.functional.db import test_migrations
19
+
20
+
21
+class TestMitaka02Mixin(test_migrations.AlembicMigrationsMixin):
22
+
23
+    def _pre_upgrade_mitaka02(self, engine):
24
+        metadef_resource_types = db_utils.get_table(engine,
25
+                                                    'metadef_resource_types')
26
+        now = datetime.datetime.now()
27
+        db_rec1 = dict(id='9580',
28
+                       name='OS::Nova::Instance',
29
+                       protected=False,
30
+                       created_at=now,
31
+                       updated_at=now,)
32
+        db_rec2 = dict(id='9581',
33
+                       name='OS::Nova::Blah',
34
+                       protected=False,
35
+                       created_at=now,
36
+                       updated_at=now,)
37
+        db_values = (db_rec1, db_rec2)
38
+        metadef_resource_types.insert().values(db_values).execute()
39
+
40
+    def _check_mitaka02(self, engine, data):
41
+        metadef_resource_types = db_utils.get_table(engine,
42
+                                                    'metadef_resource_types')
43
+        result = (metadef_resource_types.select()
44
+                  .where(metadef_resource_types.c.name == 'OS::Nova::Instance')
45
+                  .execute().fetchall())
46
+        self.assertEqual(0, len(result))
47
+
48
+        result = (metadef_resource_types.select()
49
+                  .where(metadef_resource_types.c.name == 'OS::Nova::Server')
50
+                  .execute().fetchall())
51
+        self.assertEqual(1, len(result))
52
+
53
+
54
+class TestMitaka02MySQL(TestMitaka02Mixin,
55
+                        test_base.MySQLOpportunisticTestCase):
56
+    pass
57
+
58
+
59
+class TestMitaka02PostgresSQL(TestMitaka02Mixin,
60
+                              test_base.PostgreSQLOpportunisticTestCase):
61
+    pass
62
+
63
+
64
+class TestMitaka02Sqlite(TestMitaka02Mixin, test_base.DbTestCase):
65
+    pass

+ 142
- 0
glance/tests/functional/db/migrations/test_ocata01.py View File

@@ -0,0 +1,142 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import datetime
14
+
15
+from oslo_db.sqlalchemy import test_base
16
+from oslo_db.sqlalchemy import utils as db_utils
17
+
18
+from glance.tests.functional.db import test_migrations
19
+
20
+
21
+class TestOcata01Mixin(test_migrations.AlembicMigrationsMixin):
22
+
23
+    def _pre_upgrade_ocata01(self, engine):
24
+        images = db_utils.get_table(engine, 'images')
25
+        now = datetime.datetime.now()
26
+        image_members = db_utils.get_table(engine, 'image_members')
27
+
28
+        # inserting a public image record
29
+        public_temp = dict(deleted=False,
30
+                           created_at=now,
31
+                           status='active',
32
+                           is_public=True,
33
+                           min_disk=0,
34
+                           min_ram=0,
35
+                           id='public_id')
36
+        images.insert().values(public_temp).execute()
37
+
38
+        # inserting a non-public image record for 'shared' visibility test
39
+        shared_temp = dict(deleted=False,
40
+                           created_at=now,
41
+                           status='active',
42
+                           is_public=False,
43
+                           min_disk=0,
44
+                           min_ram=0,
45
+                           id='shared_id')
46
+        images.insert().values(shared_temp).execute()
47
+
48
+        # inserting a non-public image records for 'private' visibility test
49
+        private_temp = dict(deleted=False,
50
+                            created_at=now,
51
+                            status='active',
52
+                            is_public=False,
53
+                            min_disk=0,
54
+                            min_ram=0,
55
+                            id='private_id_1')
56
+        images.insert().values(private_temp).execute()
57
+        private_temp = dict(deleted=False,
58
+                            created_at=now,
59
+                            status='active',
60
+                            is_public=False,
61
+                            min_disk=0,
62
+                            min_ram=0,
63
+                            id='private_id_2')
64
+        images.insert().values(private_temp).execute()
65
+
66
+        # adding an active as well as a deleted image member for checking
67
+        # 'shared' visibility
68
+        temp = dict(deleted=False,
69
+                    created_at=now,
70
+                    image_id='shared_id',
71
+                    member='fake_member_452',
72
+                    can_share=True,
73
+                    id=45)
74
+        image_members.insert().values(temp).execute()
75
+
76
+        temp = dict(deleted=True,
77
+                    created_at=now,
78
+                    image_id='shared_id',
79
+                    member='fake_member_453',
80
+                    can_share=True,
81
+                    id=453)
82
+        image_members.insert().values(temp).execute()
83
+
84
+        # adding an image member, but marking it deleted,
85
+        # for testing 'private' visibility
86
+        temp = dict(deleted=True,
87
+                    created_at=now,
88
+                    image_id='private_id_2',
89
+                    member='fake_member_451',
90
+                    can_share=True,
91
+                    id=451)
92
+        image_members.insert().values(temp).execute()
93
+
94
+        # adding an active image member for the 'public' image,
95
+        # to test it remains public regardless.
96
+        temp = dict(deleted=False,
97
+                    created_at=now,
98
+                    image_id='public_id',
99
+                    member='fake_member_450',
100
+                    can_share=True,
101
+                    id=450)
102
+        image_members.insert().values(temp).execute()
103
+
104
+    def _check_ocata01(self, engine, data):
105
+        # check that after migration, 'visibility' column is introduced
106
+        images = db_utils.get_table(engine, 'images')
107
+        self.assertIn('visibility', images.c)
108
+        self.assertNotIn('is_public', images.c)
109
+
110
+        # tests to identify the visibilities of images created above
111
+        rows = images.select().where(
112
+            images.c.id == 'public_id').execute().fetchall()
113
+        self.assertEqual(1, len(rows))
114
+        self.assertEqual('public', rows[0][16])
115
+
116
+        rows = images.select().where(
117
+            images.c.id == 'shared_id').execute().fetchall()
118
+        self.assertEqual(1, len(rows))
119
+        self.assertEqual('shared', rows[0][16])
120
+
121
+        rows = images.select().where(
122
+            images.c.id == 'private_id_1').execute().fetchall()
123
+        self.assertEqual(1, len(rows))
124
+        self.assertEqual('private', rows[0][16])
125
+
126
+        rows = images.select().where(
127
+            images.c.id == 'private_id_2').execute().fetchall()
128
+        self.assertEqual(1, len(rows))
129
+        self.assertEqual('private', rows[0][16])
130
+
131
+
132
+class TestOcata01MySQL(TestOcata01Mixin, test_base.MySQLOpportunisticTestCase):
133
+    pass
134
+
135
+
136
+class TestOcata01PostgresSQL(TestOcata01Mixin,
137
+                             test_base.PostgreSQLOpportunisticTestCase):
138
+    pass
139
+
140
+
141
+class TestOcata01Sqlite(TestOcata01Mixin, test_base.DbTestCase):
142
+    pass

+ 173
- 0
glance/tests/functional/db/test_migrations.py View File

@@ -0,0 +1,173 @@
1
+# Copyright 2016 Rackspace
2
+# Copyright 2016 Intel Corporation
3
+#
4
+# All Rights Reserved.
5
+#
6
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+#    not use this file except in compliance with the License. You may obtain
8
+#    a copy of the License at
9
+#
10
+#         http://www.apache.org/licenses/LICENSE-2.0
11
+#
12
+#    Unless required by applicable law or agreed to in writing, software
13
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+#    License for the specific language governing permissions and limitations
16
+#    under the License.
17
+
18
+import os
19
+
20
+from alembic import command as alembic_command
21
+from alembic import script as alembic_script
22
+from oslo_db.sqlalchemy import test_base
23
+from oslo_db.sqlalchemy import test_migrations
24
+import sqlalchemy.types as types
25
+
26
+from glance.db.sqlalchemy import alembic_migrations
27
+from glance.db.sqlalchemy.alembic_migrations import versions
28
+from glance.db.sqlalchemy import models
29
+from glance.db.sqlalchemy import models_glare
30
+from glance.db.sqlalchemy import models_metadef
31
+import glance.tests.utils as test_utils
32
+
33
+
34
+class AlembicMigrationsMixin(object):
35
+
36
+    def _get_revisions(self, config):
37
+        scripts_dir = alembic_script.ScriptDirectory.from_config(config)
38
+        revisions = list(scripts_dir.walk_revisions(base='base', head='heads'))
39
+        revisions = list(reversed(revisions))
40
+        revisions = [rev.revision for rev in revisions]
41
+        return revisions
42
+
43
+    def _migrate_up(self, config, engine, revision, with_data=False):
44
+        if with_data:
45
+            data = None
46
+            pre_upgrade = getattr(self, '_pre_upgrade_%s' % revision, None)
47
+            if pre_upgrade:
48
+                data = pre_upgrade(engine)
49
+
50
+        alembic_command.upgrade(config, revision)
51
+
52
+        if with_data:
53
+            check = getattr(self, '_check_%s' % revision, None)
54
+            if check:
55
+                check(engine, data)
56
+
57
+    def test_walk_versions(self):
58
+        alembic_config = alembic_migrations.get_alembic_config(self.engine)
59
+        for revision in self._get_revisions(alembic_config):
60
+            self._migrate_up(alembic_config, self.engine, revision,
61
+                             with_data=True)
62
+
63
+
64
+class TestMysqlMigrations(test_base.MySQLOpportunisticTestCase,
65
+                          AlembicMigrationsMixin):
66
+
67
+    def test_mysql_innodb_tables(self):
68
+        test_utils.db_sync(engine=self.engine)
69
+
70
+        total = self.engine.execute(
71
+            "SELECT COUNT(*) "
72
+            "FROM information_schema.TABLES "
73
+            "WHERE TABLE_SCHEMA='%s'"
74
+            % self.engine.url.database)
75
+        self.assertGreater(total.scalar(), 0, "No tables found. Wrong schema?")
76
+
77
+        noninnodb = self.engine.execute(
78
+            "SELECT count(*) "
79
+            "FROM information_schema.TABLES "
80
+            "WHERE TABLE_SCHEMA='%s' "
81
+            "AND ENGINE!='InnoDB' "
82
+            "AND TABLE_NAME!='migrate_version'"
83
+            % self.engine.url.database)
84
+        count = noninnodb.scalar()
85
+        self.assertEqual(0, count, "%d non InnoDB tables created" % count)
86
+
87
+
88
+class TestPostgresqlMigrations(test_base.PostgreSQLOpportunisticTestCase,
89
+                               AlembicMigrationsMixin):
90
+    pass
91
+
92
+
93
+class TestSqliteMigrations(test_base.DbTestCase, AlembicMigrationsMixin):
94
+    pass
95
+
96
+
97
+class TestMigrations(test_base.DbTestCase, test_utils.BaseTestCase):
98
+
99
+    def test_no_downgrade(self):
100
+        migrate_file = versions.__path__[0]
101
+        for parent, dirnames, filenames in os.walk(migrate_file):
102
+            for filename in filenames:
103
+                if filename.split('.')[1] == 'py':
104
+                    model_name = filename.split('.')[0]
105
+                    model = __import__(
106
+                        'glance.db.sqlalchemy.alembic_migrations.versions.' +
107
+                        model_name)
108
+                    obj = getattr(getattr(getattr(getattr(getattr(
109
+                        model, 'db'), 'sqlalchemy'), 'alembic_migrations'),
110
+                        'versions'), model_name)
111
+                    func = getattr(obj, 'downgrade', None)
112
+                    self.assertIsNone(func)
113
+
114
+
115
+class ModelsMigrationSyncMixin(object):
116
+
117
+    def get_metadata(self):
118
+        for table in models_metadef.BASE_DICT.metadata.sorted_tables:
119
+            models.BASE.metadata._add_table(table.name, table.schema, table)
120
+        for table in models_glare.BASE.metadata.sorted_tables:
121
+            models.BASE.metadata._add_table(table.name, table.schema, table)
122
+        return models.BASE.metadata
123
+
124
+    def get_engine(self):
125
+        return self.engine
126
+
127
+    def db_sync(self, engine):
128
+        test_utils.db_sync(engine=engine)
129
+
130
+    # TODO(akamyshikova): remove this method as soon as comparison with Variant
131
+    # will be implemented in oslo.db or alembic
132
+    def compare_type(self, ctxt, insp_col, meta_col, insp_type, meta_type):
133
+        if isinstance(meta_type, types.Variant):
134
+            meta_orig_type = meta_col.type
135
+            insp_orig_type = insp_col.type
136
+            meta_col.type = meta_type.impl
137
+            insp_col.type = meta_type.impl
138
+
139
+            try:
140
+                return self.compare_type(ctxt, insp_col, meta_col, insp_type,
141
+                                         meta_type.impl)
142
+            finally:
143
+                meta_col.type = meta_orig_type
144
+                insp_col.type = insp_orig_type
145
+        else:
146
+            ret = super(ModelsMigrationSyncMixin, self).compare_type(
147
+                ctxt, insp_col, meta_col, insp_type, meta_type)
148
+            if ret is not None:
149
+                return ret
150
+            return ctxt.impl.compare_type(insp_col, meta_col)
151
+
152
+    def include_object(self, object_, name, type_, reflected, compare_to):
153
+        if name in ['migrate_version'] and type_ == 'table':
154
+            return False
155
+        return True
156
+
157
+
158
+class ModelsMigrationsSyncMysql(ModelsMigrationSyncMixin,
159
+                                test_migrations.ModelsMigrationsSync,
160
+                                test_base.MySQLOpportunisticTestCase):
161
+    pass
162
+
163
+
164
+class ModelsMigrationsSyncPostgres(ModelsMigrationSyncMixin,
165
+                                   test_migrations.ModelsMigrationsSync,
166
+                                   test_base.PostgreSQLOpportunisticTestCase):
167
+    pass
168
+
169
+
170
+class ModelsMigrationsSyncSqlite(ModelsMigrationSyncMixin,
171
+                                 test_migrations.ModelsMigrationsSync,
172
+                                 test_base.DbTestCase):
173
+    pass

+ 1
- 2
glance/tests/integration/legacy_functional/base.py View File

@@ -21,7 +21,6 @@ from oslo_db import options
21 21
 
22 22
 import glance.common.client
23 23
 from glance.common import config
24
-from glance.db import migration
25 24
 import glance.db.sqlalchemy.api
26 25
 import glance.registry.client.v1.client
27 26
 from glance import tests as glance_tests
@@ -171,7 +170,7 @@ class ApiTest(test_utils.BaseTestCase):
171 170
             test_utils.execute('cp %s %s/tests.sqlite'
172 171
                                % (db_location, self.test_dir))
173 172
         else:
174
-            migration.db_sync()
173
+            test_utils.db_sync()
175 174
 
176 175
             # copy the clean db to a temp location so that it
177 176
             # can be reused for future tests

+ 1
- 2
glance/tests/integration/v2/base.py View File

@@ -24,7 +24,6 @@ from oslo_db import options
24 24
 
25 25
 import glance.common.client
26 26
 from glance.common import config
27
-from glance.db import migration
28 27
 import glance.db.sqlalchemy.api
29 28
 import glance.registry.client.v1.client
30 29
 from glance import tests as glance_tests
@@ -166,7 +165,7 @@ class ApiTest(test_utils.BaseTestCase):
166 165
             test_utils.execute('cp %s %s/tests.sqlite'
167 166
                                % (db_location, self.test_dir))
168 167
         else:
169
-            migration.db_sync()
168
+            test_utils.db_sync()
170 169
 
171 170
             # copy the clean db to a temp location so that it
172 171
             # can be reused for future tests

+ 0
- 1712
glance/tests/unit/test_migrations.py
File diff suppressed because it is too large
View File


+ 13
- 0
glance/tests/utils.py View File

@@ -23,6 +23,7 @@ import shutil
23 23
 import socket
24 24
 import subprocess
25 25
 
26
+from alembic import command as alembic_command
26 27
 import fixtures
27 28
 from oslo_config import cfg
28 29
 from oslo_config import fixture as cfg_fixture
@@ -42,6 +43,7 @@ from glance.common import timeutils
42 43
 from glance.common import utils
43 44
 from glance.common import wsgi
44 45
 from glance import context
46
+from glance.db.sqlalchemy import alembic_migrations
45 47
 from glance.db.sqlalchemy import api as db_api
46 48
 from glance.db.sqlalchemy import models as db_models
47 49
 
@@ -670,3 +672,14 @@ class HttplibWsgiAdapter(object):
670 672
         response = self.req.get_response(self.app)
671 673
         return FakeHTTPResponse(response.status_code, response.headers,
672 674
                                 response.body)
675
+
676
+
677
+def db_sync(version=None, engine=None):
678
+    """Migrate the database to `version` or the most recent version."""
679
+    if version is None:
680
+        version = 'heads'
681
+    if engine is None:
682
+        engine = db_api.get_engine()
683
+
684
+    alembic_config = alembic_migrations.get_alembic_config(engine=engine)
685
+    alembic_command.upgrade(alembic_config, version)

+ 9
- 0
tox.ini View File

@@ -10,6 +10,15 @@ basepython =
10 10
 setenv =
11 11
   VIRTUAL_ENV={envdir}
12 12
   PYTHONWARNINGS=default::DeprecationWarning
13
+# NOTE(hemanthm): The environment variable "OS_TEST_DBAPI_ADMIN_CONNECTION"
14
+# must be set to force oslo.db tests to use a file-based sqlite database
15
+# instead of the default in-memory database, which doesn't work well with
16
+# alembic migrations. The file-based database pointed by the environment
17
+# variable itself is not used for testing. Neither is it ever created. Oslo.db
18
+# creates another file-based database for testing purposes and deletes it as a
19
+# part of its test clean-up. Think of setting this environment variable as a
20
+# clue for oslo.db to use file-based database.
21
+  OS_TEST_DBAPI_ADMIN_CONNECTION=sqlite:////tmp/placeholder-never-created-nor-used.db
13 22
 usedevelop = True
14 23
 install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
15 24
 deps = -r{toxinidir}/test-requirements.txt

Loading…
Cancel
Save