Coverage for tests/test_versioning.py: 31%

108 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-25 10:24 -0700

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/>. 

27 

28import os 

29import os.path 

30import tempfile 

31import unittest 

32 

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 

43 

44TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

45 

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) 

52 

53 

54class Manager0(VersionedExtension): 

55 """Versioned extension implementation for tests.""" 

56 

57 @classmethod 

58 def currentVersions(cls) -> list[VersionTuple]: 

59 return [] 

60 

61 

62class Manager1(VersionedExtension): 

63 """Versioned extension implementation for tests.""" 

64 

65 @classmethod 

66 def currentVersions(cls) -> list[VersionTuple]: 

67 return [V_1_0_0] 

68 

69 

70class Manager1_1(VersionedExtension): # noqa: N801 

71 """Versioned extension implementation for tests.""" 

72 

73 @classmethod 

74 def currentVersions(cls) -> list[VersionTuple]: 

75 return [V_1_1_0] 

76 

77 

78class Manager2(VersionedExtension): 

79 """Versioned extension implementation for tests. 

80 

81 This extension supports two schema versions. 

82 """ 

83 

84 @classmethod 

85 def currentVersions(cls) -> list[VersionTuple]: 

86 return [V_1_0_0, V_2_0_0] 

87 

88 @classmethod 

89 def _newDefaultSchemaVersion(cls) -> VersionTuple: 

90 return V_1_0_0 

91 

92 

93class SchemaVersioningTestCase(unittest.TestCase): 

94 """Tests for schema versioning classes.""" 

95 

96 def setUp(self): 

97 self.root = makeTestTempDir(TESTDIR) 

98 

99 def tearDown(self): 

100 removeTestTempDir(self.root) 

101 

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) 

106 

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) 

119 

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 ) 

125 

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) 

137 

138 database = self.makeEmptyDatabase() 

139 with database.declareStaticTables(create=True) as context: 

140 attributes = DefaultButlerAttributeManager.initialize(database, context) 

141 

142 vmgr = ButlerVersionsManager(attributes) 

143 vmgr.storeManagersConfig({"manager0": manager0, "manager1": manager1, "manager2": manager2}) 

144 

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) 

154 

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 ) 

161 

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) 

171 

172 # Create new schema first. 

173 database = self.makeEmptyDatabase() 

174 with database.declareStaticTables(create=True) as context: 

175 attributes = DefaultButlerAttributeManager.initialize(database, context) 

176 

177 vmgr = ButlerVersionsManager(attributes) 

178 vmgr.storeManagersConfig({"manager0": manager0, "manager1": manager1, "manager2": manager2}) 

179 

180 # Switch to reading existing manager configs/versions. 

181 with database.declareStaticTables(create=False) as context: 

182 attributes = DefaultButlerAttributeManager.initialize(database, context) 

183 

184 vmgr = ButlerVersionsManager(attributes) 

185 vmgr.checkManagersConfig({"manager0": Manager0, "manager1": Manager1, "manager2": Manager2}) 

186 versions = vmgr.managerVersions() 

187 

188 Manager1.checkCompatibility(result1, database.isWriteable()) 

189 Manager2.checkCompatibility(result2, database.isWriteable()) 

190 

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) 

198 

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 ) 

228 

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) 

236 

237 

238if __name__ == "__main__": 

239 unittest.main()