Coverage for python/lsst/verify/tasks/testUtils.py: 26%

98 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 22:32 -0800

1# This file is part of verify. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

21 

22__all__ = ["MetadataMetricTestCase", "ApdbMetricTestCase"] 

23 

24import abc 

25 

26import unittest.mock 

27from unittest.mock import patch 

28 

29import lsst.utils.tests 

30from lsst.pipe.base import TaskMetadata 

31from lsst.dax.apdb import ApdbConfig 

32 

33import lsst.verify.gen2tasks.testUtils as gen2Utils 

34from lsst.verify.tasks import MetricComputationError 

35 

36 

37class MetricTaskTestCase(lsst.utils.tests.TestCase, metaclass=abc.ABCMeta): 

38 """Unit test base class for tests of `tasks.MetricTask`. 

39 

40 This class provides tests of the generic ``MetricTask`` API. Subclasses 

41 must override `makeTask`, and may add extra tests for class-specific 

42 functionality. If subclasses override `setUp`, they must call 

43 `MetricTaskTestCase.setUp`. 

44 """ 

45 @classmethod 

46 @abc.abstractmethod 

47 def makeTask(cls): 

48 """Construct the task to be tested. 

49 

50 This overridable method will be called during test setup. 

51 

52 Returns 

53 ------- 

54 task : `lsst.verify.tasks.MetricTask` 

55 A new MetricTask object to test. 

56 """ 

57 

58 task = None 

59 """The ``MetricTask`` being tested by this object 

60 (`tasks.MetricTask`). 

61 

62 This attribute is initialized automatically. 

63 """ 

64 

65 taskClass = None 

66 """The type of `task` (`tasks.MetricTask`-type). 

67 

68 This attribute is initialized automatically. 

69 """ 

70 

71 def setUp(self): 

72 """Setup common to all MetricTask tests. 

73 

74 Notes 

75 ----- 

76 This implementation calls `makeTask`, then initializes `task` 

77 and `taskClass`. 

78 """ 

79 self.task = self.makeTask() 

80 self.taskClass = type(self.task) 

81 

82 def testOutputDatasetName(self): 

83 config = self.task.config 

84 connections = config.connections.ConnectionsClass(config=config) 

85 dataset = connections.measurement.name 

86 

87 self.assertTrue(dataset.startswith("metricvalue_")) 

88 self.assertNotIn(".", dataset) 

89 

90 self.assertIn(config.connections.package, dataset) 

91 self.assertIn(config.connections.metric, dataset) 

92 

93 def testConfigValidation(self): 

94 config = self.task.config 

95 config.connections.metric = "verify.DummyMetric" 

96 with self.assertRaises(ValueError): 

97 config.validate() 

98 

99 

100class MetadataMetricTestCase(gen2Utils.MetricTaskTestCase, MetricTaskTestCase): 

101 """Unit test base class for tests of `MetadataMetricTask`. 

102 

103 Notes 

104 ----- 

105 Subclasses must override 

106 `~lsst.verify.gen2tasks.MetricTaskTestCase.makeTask` for the concrete task 

107 being tested. 

108 """ 

109 

110 @staticmethod 

111 def _takesScalarMetadata(task): 

112 return task.areInputDatasetsScalar(task.config)['metadata'] 

113 

114 def testValidRun(self): 

115 """Test how run delegates to the abstract methods. 

116 """ 

117 mockKey = "unitTestKey" 

118 with patch.object(self.task, "getInputMetadataKeys", 

119 return_value={"unused": mockKey}), \ 

120 patch.object(self.task, "makeMeasurement") as mockWorkhorse: 

121 if self._takesScalarMetadata(self.task): 

122 metadata1 = TaskMetadata() 

123 metadata1[mockKey] = 42 

124 

125 self.task.run(metadata1) 

126 mockWorkhorse.assert_called_once_with({"unused": 42}) 

127 mockWorkhorse.reset_mock() 

128 self.task.run(None) 

129 mockWorkhorse.assert_called_once_with({"unused": None}) 

130 else: 

131 metadata1 = TaskMetadata() 

132 metadata1[mockKey] = 42 

133 metadata2 = TaskMetadata() 

134 metadata2[mockKey] = "Sphere" 

135 self.task.run([metadata1, None, metadata2]) 

136 mockWorkhorse.assert_called_once_with( 

137 [{"unused": value} for value in [42, None, "Sphere"]]) 

138 

139 def testAmbiguousRun(self): 

140 mockKey = "unitTestKey" 

141 with patch.object(self.task, "getInputMetadataKeys", 

142 return_value={"unused": mockKey}): 

143 metadata = TaskMetadata() 

144 metadata[mockKey + "1"] = 42 

145 metadata[mockKey + "2"] = "Sphere" 

146 with self.assertRaises(MetricComputationError): 

147 if self._takesScalarMetadata(self.task): 

148 self.task.run(metadata) 

149 else: 

150 self.task.run([metadata]) 

151 

152 def testPassThroughRun(self): 

153 with patch.object(self.task, "makeMeasurement", 

154 side_effect=MetricComputationError): 

155 with self.assertRaises(MetricComputationError): 

156 if self._takesScalarMetadata(self.task): 

157 self.task.run(None) 

158 else: 

159 self.task.run([None]) 

160 

161 def testDimensionsOverride(self): 

162 config = self.task.config 

163 expectedDimensions = {"instrument", "visit"} 

164 config.metadataDimensions = expectedDimensions 

165 

166 connections = config.connections.ConnectionsClass(config=config) 

167 self.assertSetEqual(set(connections.dimensions), 

168 expectedDimensions) 

169 self.assertIn(connections.metadata, 

170 connections.allConnections.values()) 

171 self.assertSetEqual(set(connections.metadata.dimensions), 

172 expectedDimensions) 

173 

174 

175class ApdbMetricTestCase(gen2Utils.MetricTaskTestCase, MetricTaskTestCase): 

176 """Unit test base class for tests of `ApdbMetricTask`. 

177 

178 Notes 

179 ----- 

180 Subclasses must override 

181 `~lsst.verify.gen2tasks.MetricTaskTestCase.makeTask` for the concrete task 

182 being tested. Subclasses that use a custom DbLoader should also 

183 override `makeDbInfo`. 

184 """ 

185 

186 @classmethod 

187 def makeDbInfo(cls): 

188 """Return an object that can be passed as input to an `ApdbMetricTask`. 

189 

190 This method is intended for generic tests that simply need to call 

191 ``run`` on some valid input. If a test depends on specific input, it 

192 should create that input directly. 

193 

194 The default implementation creates a `~lsst.pex.config.Config` that 

195 will be accepted by `~lsst.verify.tasks.DirectApdbLoader`. Test suites 

196 that use a different loader should override this method. 

197 """ 

198 return ApdbConfig() 

199 

200 def testValidRun(self): 

201 info = self.makeDbInfo() 

202 with patch.object(self.task, "makeMeasurement") as mockWorkhorse: 

203 self.task.run([info]) 

204 mockWorkhorse.assert_called_once() 

205 

206 def testDataIdRun(self): 

207 info = self.makeDbInfo() 

208 with patch.object(self.task, "makeMeasurement") as mockWorkhorse: 

209 dataId = {'visit': 42} 

210 self.task.run([info], outputDataId=dataId) 

211 mockWorkhorse.assert_called_once_with( 

212 unittest.mock.ANY, {'visit': 42}) 

213 

214 def testPassThroughRun(self): 

215 with patch.object(self.task, "makeMeasurement", 

216 side_effect=MetricComputationError): 

217 info = self.makeDbInfo() 

218 with self.assertRaises(MetricComputationError): 

219 self.task.run([info])