Coverage for tests/test_apdbSql.py: 44%

130 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-26 09:55 +0000

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 allow_visit_query = False 

52 schema_path = TEST_SCHEMA 

53 

54 def setUp(self) -> None: 

55 self.tempdir = tempfile.mkdtemp() 

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

57 

58 def tearDown(self) -> None: 

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

60 

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

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

63 kw = { 

64 "db_url": self.db_url, 

65 "schema_file": TEST_SCHEMA, 

66 "dia_object_index": self.dia_object_index, 

67 "use_insert_id": self.enable_replica, 

68 } 

69 kw.update(kwargs) 

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

71 

72 def getDiaObjects_table(self) -> ApdbTables: 

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

74 return ApdbTables.DiaObject 

75 

76 

77class ApdbSQLiteTestCaseLastObject(ApdbSQLiteTestCase): 

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

79 table. 

80 """ 

81 

82 dia_object_index = "last_object_table" 

83 

84 def getDiaObjects_table(self) -> ApdbTables: 

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

86 return ApdbTables.DiaObjectLast 

87 

88 

89class ApdbSQLiteTestCasePixIdIovIndex(ApdbSQLiteTestCase): 

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

91 indexing. 

92 """ 

93 

94 dia_object_index = "pix_id_iov" 

95 

96 

97class ApdbSQLiteTestCaseReplica(ApdbSQLiteTestCase): 

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

99 

100 enable_replica = True 

101 

102 

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

104class ApdbPostgresTestCase(ApdbTest, unittest.TestCase): 

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

106 

107 fsrc_requires_id_list = True 

108 dia_object_index = "last_object_table" 

109 postgresql: Any 

110 enable_replica = True 

111 allow_visit_query = False 

112 schema_path = TEST_SCHEMA 

113 

114 @classmethod 

115 def setUpClass(cls) -> None: 

116 # Create the postgres test server. 

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

118 super().setUpClass() 

119 

120 @classmethod 

121 def tearDownClass(cls) -> None: 

122 # Clean up any lingering SQLAlchemy engines/connections 

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

124 gc.collect() 

125 cls.postgresql.clear_cache() 

126 super().tearDownClass() 

127 

128 def setUp(self) -> None: 

129 self.server = self.postgresql() 

130 

131 def tearDown(self) -> None: 

132 self.server = self.postgresql() 

133 

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

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

136 kw = { 

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

138 "schema_file": TEST_SCHEMA, 

139 "dia_object_index": self.dia_object_index, 

140 "use_insert_id": self.enable_replica, 

141 } 

142 kw.update(kwargs) 

143 return ApdbSql.init_database(**kw) 

144 

145 def getDiaObjects_table(self) -> ApdbTables: 

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

147 return ApdbTables.DiaObjectLast 

148 

149 

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

151class ApdbPostgresNamespaceTestCase(ApdbPostgresTestCase): 

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

153 

154 # use mixed case to trigger quoting 

155 namespace = "ApdbSchema" 

156 

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

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

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

160 

161 

162class ApdbSchemaUpdateSQLiteTestCase(ApdbSchemaUpdateTest, unittest.TestCase): 

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

164 

165 def setUp(self) -> None: 

166 self.tempdir = tempfile.mkdtemp() 

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

168 

169 def tearDown(self) -> None: 

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

171 

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

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

174 kw = { 

175 "db_url": self.db_url, 

176 "schema_file": TEST_SCHEMA, 

177 } 

178 kw.update(kwargs) 

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

180 

181 

182class ApdbSQLiteFromUriTestCase(unittest.TestCase): 

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

184 

185 def setUp(self) -> None: 

186 self.tempdir = tempfile.mkdtemp() 

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

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

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

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

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

192 config.save(self.config_path) 

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

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

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

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

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

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

199 # File with incorrect format. 

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

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

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

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

204 

205 def test_make_apdb_from_path(self) -> None: 

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

207 Apdb.from_uri(self.config_path) 

208 with self.assertRaises(FileNotFoundError): 

209 Apdb.from_uri(self.bad_config_path) 

210 

211 def test_make_apdb_from_labels(self) -> None: 

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

213 # Replace DAX_APDB_INDEX_URI value 

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

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

216 Apdb.from_uri("label:label1") 

217 Apdb.from_uri("label:label2") 

218 # Label does not exist. 

219 with self.assertRaises(ValueError): 

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

221 # Label exists but points to a missing config. 

222 with self.assertRaises(FileNotFoundError): 

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

224 

225 def test_make_apdb_bad_index(self) -> None: 

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

227 # envvar is set but empty. 

228 new_env = {"DAX_APDB_INDEX_URI": ""} 

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

230 with self.assertRaises(RuntimeError): 

231 Apdb.from_uri("label:label") 

232 

233 # envvar is set to something non-existing. 

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

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

236 with self.assertRaises(FileNotFoundError): 

237 Apdb.from_uri("label:label") 

238 

239 # envvar points to an incorrect file. 

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

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

242 with self.assertRaises(TypeError): 

243 Apdb.from_uri("label:label") 

244 

245 

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

247 """Run file leak tests.""" 

248 

249 

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

251 """Configure pytest.""" 

252 lsst.utils.tests.init() 

253 

254 

255if __name__ == "__main__": 

256 lsst.utils.tests.init() 

257 unittest.main()