Coverage for tests/test_versioning.py: 31%
108 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-25 10:50 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-25 10:50 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
28import os
29import os.path
30import tempfile
31import unittest
33from lsst.daf.butler.registry.attributes import DefaultButlerAttributeManager
34from lsst.daf.butler.registry.databases.sqlite import SqliteDatabase
35from lsst.daf.butler.registry.interfaces import (
36 Database,
37 IncompatibleVersionError,
38 VersionedExtension,
39 VersionTuple,
40)
41from lsst.daf.butler.registry.versions import ButlerVersionsManager
42from lsst.daf.butler.tests.utils import makeTestTempDir, removeTestTempDir
44TESTDIR = os.path.abspath(os.path.dirname(__file__))
46# Assorted version numbers used throughout the tests
47V_1_0_0 = VersionTuple(major=1, minor=0, patch=0)
48V_1_0_1 = VersionTuple(major=1, minor=0, patch=1)
49V_1_1_0 = VersionTuple(major=1, minor=1, patch=0)
50V_2_0_0 = VersionTuple(major=2, minor=0, patch=0)
51V_2_0_1 = VersionTuple(major=2, minor=0, patch=1)
54class Manager0(VersionedExtension):
55 """Versioned extension implementation for tests."""
57 @classmethod
58 def currentVersions(cls) -> list[VersionTuple]:
59 return []
62class Manager1(VersionedExtension):
63 """Versioned extension implementation for tests."""
65 @classmethod
66 def currentVersions(cls) -> list[VersionTuple]:
67 return [V_1_0_0]
70class Manager1_1(VersionedExtension): # noqa: N801
71 """Versioned extension implementation for tests."""
73 @classmethod
74 def currentVersions(cls) -> list[VersionTuple]:
75 return [V_1_1_0]
78class Manager2(VersionedExtension):
79 """Versioned extension implementation for tests.
81 This extension supports two schema versions.
82 """
84 @classmethod
85 def currentVersions(cls) -> list[VersionTuple]:
86 return [V_1_0_0, V_2_0_0]
88 @classmethod
89 def _newDefaultSchemaVersion(cls) -> VersionTuple:
90 return V_1_0_0
93class SchemaVersioningTestCase(unittest.TestCase):
94 """Tests for schema versioning classes."""
96 def setUp(self):
97 self.root = makeTestTempDir(TESTDIR)
99 def tearDown(self):
100 removeTestTempDir(self.root)
102 def makeEmptyDatabase(self, origin: int = 0) -> Database:
103 _, filename = tempfile.mkstemp(dir=self.root, suffix=".sqlite3")
104 engine = SqliteDatabase.makeEngine(filename=filename)
105 return SqliteDatabase.fromEngine(engine=engine, origin=origin)
107 def test_new_schema(self) -> None:
108 """Test for creating new database schema."""
109 # Check that managers know what schema versions they can make.
110 Manager1.checkNewSchemaVersion(V_1_0_0)
111 Manager2.checkNewSchemaVersion(V_1_0_0)
112 Manager2.checkNewSchemaVersion(V_2_0_0)
113 with self.assertRaises(IncompatibleVersionError):
114 Manager1.checkNewSchemaVersion(V_1_0_1)
115 with self.assertRaises(IncompatibleVersionError):
116 Manager1.checkNewSchemaVersion(V_1_1_0)
117 with self.assertRaises(IncompatibleVersionError):
118 Manager2.checkNewSchemaVersion(V_1_0_1)
120 manager_versions = (
121 ((None, V_1_0_0), (None, V_1_0_0)),
122 ((V_1_0_0, V_1_0_0), (V_1_0_0, V_1_0_0)),
123 ((None, V_1_0_0), (V_2_0_0, V_2_0_0)),
124 )
126 for (v1, result1), (v2, result2) in manager_versions:
127 # This is roughly what RegistryManagerTypes.makeRepo does.
128 if v1 is not None:
129 Manager1.checkNewSchemaVersion(v1)
130 if v2 is not None:
131 Manager2.checkNewSchemaVersion(v2)
132 manager0 = Manager0()
133 manager1 = Manager1(registry_schema_version=v1)
134 manager2 = Manager2(registry_schema_version=v2)
135 self.assertEqual(manager1.newSchemaVersion(), result1)
136 self.assertEqual(manager2.newSchemaVersion(), result2)
138 database = self.makeEmptyDatabase()
139 with database.declareStaticTables(create=True) as context:
140 attributes = DefaultButlerAttributeManager.initialize(database, context)
142 vmgr = ButlerVersionsManager(attributes)
143 vmgr.storeManagersConfig({"manager0": manager0, "manager1": manager1, "manager2": manager2})
145 attr_dict = dict(attributes.items())
146 expected = {
147 "config:registry.managers.manager0": Manager0.extensionName(),
148 "config:registry.managers.manager1": Manager1.extensionName(),
149 "config:registry.managers.manager2": Manager2.extensionName(),
150 f"version:{Manager1.extensionName()}": str(result1),
151 f"version:{Manager2.extensionName()}": str(result2),
152 }
153 self.assertEqual(attr_dict, expected)
155 def test_existing_schema(self) -> None:
156 """Test for reading manager versions from existing database."""
157 manager_versions = (
158 ((None, V_1_0_0), (None, V_1_0_0)),
159 ((V_1_0_0, V_1_0_0), (V_1_0_0, V_1_0_0)),
160 )
162 for (v1, result1), (v2, result2) in manager_versions:
163 # This is roughly what RegistryManagerTypes.loadRepo does.
164 if v1 is not None:
165 Manager1.checkNewSchemaVersion(v1)
166 if v2 is not None:
167 Manager2.checkNewSchemaVersion(v2)
168 manager0 = Manager0()
169 manager1 = Manager1(registry_schema_version=v1)
170 manager2 = Manager2(registry_schema_version=v2)
172 # Create new schema first.
173 database = self.makeEmptyDatabase()
174 with database.declareStaticTables(create=True) as context:
175 attributes = DefaultButlerAttributeManager.initialize(database, context)
177 vmgr = ButlerVersionsManager(attributes)
178 vmgr.storeManagersConfig({"manager0": manager0, "manager1": manager1, "manager2": manager2})
180 # Switch to reading existing manager configs/versions.
181 with database.declareStaticTables(create=False) as context:
182 attributes = DefaultButlerAttributeManager.initialize(database, context)
184 vmgr = ButlerVersionsManager(attributes)
185 vmgr.checkManagersConfig({"manager0": Manager0, "manager1": Manager1, "manager2": Manager2})
186 versions = vmgr.managerVersions()
188 Manager1.checkCompatibility(result1, database.isWriteable())
189 Manager2.checkCompatibility(result2, database.isWriteable())
191 # Make manager instances using versions from registry.
192 manager0 = Manager0(registry_schema_version=versions.get("manager0"))
193 manager1 = Manager1(registry_schema_version=versions.get("manager1"))
194 manager2 = Manager2(registry_schema_version=versions.get("manager2"))
195 self.assertIsNone(manager0._registry_schema_version)
196 self.assertEqual(manager1._registry_schema_version, result1)
197 self.assertEqual(manager2._registry_schema_version, result2)
199 def test_compatibility(self) -> None:
200 """Test for version compatibility rules."""
201 # Manager, version, update, compatible
202 compat_matrix = (
203 (Manager0, V_1_0_0, False, True),
204 (Manager0, V_1_0_0, True, True),
205 (Manager1, V_1_0_0, False, True),
206 (Manager1, V_1_0_0, True, True),
207 (Manager1, V_1_0_1, False, True),
208 (Manager1, V_1_0_1, True, True),
209 (Manager1, V_1_1_0, False, False),
210 (Manager1, V_1_1_0, True, False),
211 (Manager1, V_2_0_0, False, False),
212 (Manager1, V_2_0_0, True, False),
213 (Manager1_1, V_1_0_0, False, True),
214 (Manager1_1, V_1_0_0, True, False),
215 (Manager1_1, V_1_0_1, False, True),
216 (Manager1_1, V_1_0_1, True, False),
217 (Manager1_1, V_1_1_0, False, True),
218 (Manager1_1, V_1_1_0, True, True),
219 (Manager2, V_1_0_0, False, True),
220 (Manager2, V_1_0_0, True, True),
221 (Manager2, V_1_0_1, False, True),
222 (Manager2, V_1_0_1, True, True),
223 (Manager2, V_1_1_0, False, False),
224 (Manager2, V_1_1_0, True, False),
225 (Manager2, V_2_0_0, False, True),
226 (Manager2, V_2_0_0, True, True),
227 )
229 for Manager, version, update, compatible in compat_matrix:
230 with self.subTest(test=(Manager, version, update, compatible)):
231 if compatible:
232 Manager.checkCompatibility(version, update)
233 else:
234 with self.assertRaises(IncompatibleVersionError):
235 Manager.checkCompatibility(version, update)
238if __name__ == "__main__":
239 unittest.main()