Coverage for tests/test_apdbSql.py: 44%

128 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-08 02:52 -0700

1# This file is part of dax_apdb. 

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 program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21 

22"""Unit test for Apdb class. 

23""" 

24 

25import gc 

26import os 

27import shutil 

28import tempfile 

29import unittest 

30from typing import Any 

31from unittest.mock import patch 

32 

33import lsst.utils.tests 

34from lsst.dax.apdb import Apdb, ApdbConfig, ApdbTables 

35from lsst.dax.apdb.sql import ApdbSql 

36from lsst.dax.apdb.tests import ApdbSchemaUpdateTest, ApdbTest 

37 

38try: 

39 import testing.postgresql 

40except ImportError: 

41 testing = None 

42 

43TEST_SCHEMA = os.path.join(os.path.abspath(os.path.dirname(__file__)), "config/schema.yaml") 

44 

45 

46class ApdbSQLiteTestCase(ApdbTest, unittest.TestCase): 

47 """A test case for ApdbSql class using SQLite backend.""" 

48 

49 fsrc_requires_id_list = True 

50 dia_object_index = "baseline" 

51 schema_path = TEST_SCHEMA 

52 

53 def setUp(self) -> None: 

54 self.tempdir = tempfile.mkdtemp() 

55 self.db_url = f"sqlite:///{self.tempdir}/apdb.sqlite3" 

56 

57 def tearDown(self) -> None: 

58 shutil.rmtree(self.tempdir, ignore_errors=True) 

59 

60 def make_instance(self, **kwargs: Any) -> ApdbConfig: 

61 """Make config class instance used in all tests.""" 

62 kw = { 

63 "db_url": self.db_url, 

64 "schema_file": TEST_SCHEMA, 

65 "dia_object_index": self.dia_object_index, 

66 "use_insert_id": self.enable_replica, 

67 } 

68 kw.update(kwargs) 

69 return ApdbSql.init_database(**kw) # type: ignore[arg-type] 

70 

71 def getDiaObjects_table(self) -> ApdbTables: 

72 """Return type of table returned from getDiaObjects method.""" 

73 return ApdbTables.DiaObject 

74 

75 

76class ApdbSQLiteTestCaseLastObject(ApdbSQLiteTestCase): 

77 """A test case for ApdbSql class using SQLite backend and DiaObjectLast 

78 table. 

79 """ 

80 

81 dia_object_index = "last_object_table" 

82 

83 def getDiaObjects_table(self) -> ApdbTables: 

84 """Return type of table returned from getDiaObjects method.""" 

85 return ApdbTables.DiaObjectLast 

86 

87 

88class ApdbSQLiteTestCasePixIdIovIndex(ApdbSQLiteTestCase): 

89 """A test case for ApdbSql class using SQLite backend with pix_id_iov 

90 indexing. 

91 """ 

92 

93 dia_object_index = "pix_id_iov" 

94 

95 

96class ApdbSQLiteTestCaseReplica(ApdbSQLiteTestCase): 

97 """Test case for ApdbSql class using SQLite backend with replica tables.""" 

98 

99 enable_replica = True 

100 

101 

102@unittest.skipUnless(testing is not None, "testing.postgresql module not found") 

103class ApdbPostgresTestCase(ApdbTest, unittest.TestCase): 

104 """A test case for ApdbSql class using Postgres backend.""" 

105 

106 fsrc_requires_id_list = True 

107 dia_object_index = "last_object_table" 

108 postgresql: Any 

109 enable_replica = True 

110 schema_path = TEST_SCHEMA 

111 

112 @classmethod 

113 def setUpClass(cls) -> None: 

114 # Create the postgres test server. 

115 cls.postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True) 

116 super().setUpClass() 

117 

118 @classmethod 

119 def tearDownClass(cls) -> None: 

120 # Clean up any lingering SQLAlchemy engines/connections 

121 # so they're closed before we shut down the server. 

122 gc.collect() 

123 cls.postgresql.clear_cache() 

124 super().tearDownClass() 

125 

126 def setUp(self) -> None: 

127 self.server = self.postgresql() 

128 

129 def tearDown(self) -> None: 

130 self.server = self.postgresql() 

131 

132 def make_instance(self, **kwargs: Any) -> ApdbConfig: 

133 """Make config class instance used in all tests.""" 

134 kw = { 

135 "db_url": self.server.url(), 

136 "schema_file": TEST_SCHEMA, 

137 "dia_object_index": self.dia_object_index, 

138 "use_insert_id": self.enable_replica, 

139 } 

140 kw.update(kwargs) 

141 return ApdbSql.init_database(**kw) 

142 

143 def getDiaObjects_table(self) -> ApdbTables: 

144 """Return type of table returned from getDiaObjects method.""" 

145 return ApdbTables.DiaObjectLast 

146 

147 

148@unittest.skipUnless(testing is not None, "testing.postgresql module not found") 

149class ApdbPostgresNamespaceTestCase(ApdbPostgresTestCase): 

150 """A test case for ApdbSql class using Postgres backend with schema name""" 

151 

152 # use mixed case to trigger quoting 

153 namespace = "ApdbSchema" 

154 

155 def make_instance(self, **kwargs: Any) -> ApdbConfig: 

156 """Make config class instance used in all tests.""" 

157 return super().make_instance(namespace=self.namespace, **kwargs) 

158 

159 

160class ApdbSchemaUpdateSQLiteTestCase(ApdbSchemaUpdateTest, unittest.TestCase): 

161 """A test case for schema updates using SQLite backend.""" 

162 

163 def setUp(self) -> None: 

164 self.tempdir = tempfile.mkdtemp() 

165 self.db_url = f"sqlite:///{self.tempdir}/apdb.sqlite3" 

166 

167 def tearDown(self) -> None: 

168 shutil.rmtree(self.tempdir, ignore_errors=True) 

169 

170 def make_instance(self, **kwargs: Any) -> ApdbConfig: 

171 """Make config class instance used in all tests.""" 

172 kw = { 

173 "db_url": self.db_url, 

174 "schema_file": TEST_SCHEMA, 

175 } 

176 kw.update(kwargs) 

177 return ApdbSql.init_database(**kw) # type: ignore[arg-type] 

178 

179 

180class ApdbSQLiteFromUriTestCase(unittest.TestCase): 

181 """A test case for for instantiating ApdbSql via URI.""" 

182 

183 def setUp(self) -> None: 

184 self.tempdir = tempfile.mkdtemp() 

185 self.addCleanup(shutil.rmtree, self.tempdir, ignore_errors=True) 

186 self.db_url = f"sqlite:///{self.tempdir}/apdb.sqlite3" 

187 config = ApdbSql.init_database(db_url=self.db_url, schema_file=TEST_SCHEMA) 

188 # TODO: This will need update when we switch to pydantic configs. 

189 self.config_path = os.path.join(self.tempdir, "apdb-config.py") 

190 config.save(self.config_path) 

191 self.bad_config_path = os.path.join(self.tempdir, "not-config.py") 

192 self.index_path = os.path.join(self.tempdir, "apdb-index.yaml") 

193 with open(self.index_path, "w") as index_file: 

194 print(f'label1: "{self.config_path}"', file=index_file) 

195 print(f'"label2/pex_config": "{self.config_path}"', file=index_file) 

196 print(f'bad-label: "{self.bad_config_path}"', file=index_file) 

197 # File with incorrect format. 

198 self.bad_index_path = os.path.join(self.tempdir, "apdb-index-bad.yaml") 

199 with open(self.bad_index_path, "w") as index_file: 

200 print(f'label1: ["{self.config_path}"]', file=index_file) 

201 self.missing_index_path = os.path.join(self.tempdir, "no-apdb-index.yaml") 

202 

203 def test_make_apdb_from_path(self) -> None: 

204 """Check that we can make APDB instance from config URI.""" 

205 Apdb.from_uri(self.config_path) 

206 with self.assertRaises(FileNotFoundError): 

207 Apdb.from_uri(self.bad_config_path) 

208 

209 def test_make_apdb_from_labels(self) -> None: 

210 """Check that we can make APDB instance from config URI.""" 

211 # Replace DAX_APDB_INDEX_URI value 

212 new_env = {"DAX_APDB_INDEX_URI": self.index_path} 

213 with patch.dict(os.environ, new_env, clear=True): 

214 Apdb.from_uri("label:label1") 

215 Apdb.from_uri("label:label2") 

216 # Label does not exist. 

217 with self.assertRaises(ValueError): 

218 Apdb.from_uri("label:not-a-label") 

219 # Label exists but points to a missing config. 

220 with self.assertRaises(FileNotFoundError): 

221 Apdb.from_uri("label:bad-label") 

222 

223 def test_make_apdb_bad_index(self) -> None: 

224 """Check what happens when DAX_APDB_INDEX_URI is broken.""" 

225 # envvar is set but empty. 

226 new_env = {"DAX_APDB_INDEX_URI": ""} 

227 with patch.dict(os.environ, new_env, clear=True): 

228 with self.assertRaises(RuntimeError): 

229 Apdb.from_uri("label:label") 

230 

231 # envvar is set to something non-existing. 

232 new_env = {"DAX_APDB_INDEX_URI": self.missing_index_path} 

233 with patch.dict(os.environ, new_env, clear=True): 

234 with self.assertRaises(FileNotFoundError): 

235 Apdb.from_uri("label:label") 

236 

237 # envvar points to an incorrect file. 

238 new_env = {"DAX_APDB_INDEX_URI": self.bad_index_path} 

239 with patch.dict(os.environ, new_env, clear=True): 

240 with self.assertRaises(TypeError): 

241 Apdb.from_uri("label:label") 

242 

243 

244class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

245 """Run file leak tests.""" 

246 

247 

248def setup_module(module: Any) -> None: 

249 """Configure pytest.""" 

250 lsst.utils.tests.init() 

251 

252 

253if __name__ == "__main__": 

254 lsst.utils.tests.init() 

255 unittest.main()