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

97 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-10 10:11 +0000

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 

33from lsst.verify.tasks import MetricComputationError 

34 

35 

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

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

38 

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

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

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

42 `MetricTaskTestCase.setUp`. 

43 """ 

44 @classmethod 

45 @abc.abstractmethod 

46 def makeTask(cls): 

47 """Construct the task to be tested. 

48 

49 This overridable method will be called during test setup. 

50 

51 Returns 

52 ------- 

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

54 A new MetricTask object to test. 

55 """ 

56 

57 task = None 

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

59 (`tasks.MetricTask`). 

60 

61 This attribute is initialized automatically. 

62 """ 

63 

64 taskClass = None 

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

66 

67 This attribute is initialized automatically. 

68 """ 

69 

70 def setUp(self): 

71 """Setup common to all MetricTask tests. 

72 

73 Notes 

74 ----- 

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

76 and `taskClass`. 

77 """ 

78 self.task = self.makeTask() 

79 self.taskClass = type(self.task) 

80 

81 def testOutputDatasetName(self): 

82 config = self.task.config 

83 connections = config.connections.ConnectionsClass(config=config) 

84 dataset = connections.measurement.name 

85 

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

87 self.assertNotIn(".", dataset) 

88 

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

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

91 

92 def testConfigValidation(self): 

93 config = self.task.config 

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

95 with self.assertRaises(ValueError): 

96 config.validate() 

97 

98 

99class MetadataMetricTestCase(MetricTaskTestCase): 

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

101 

102 Notes 

103 ----- 

104 Subclasses must override 

105 `~lsst.verify.tasks.MetricTaskTestCase.makeTask` for the concrete task 

106 being tested. 

107 """ 

108 

109 @staticmethod 

110 def _takesScalarMetadata(task): 

111 connections = task.config.connections.ConnectionsClass(config=task.config) 

112 return not connections.metadata.multiple 

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 else: 

128 metadata1 = TaskMetadata() 

129 metadata1[mockKey] = 42 

130 metadata2 = TaskMetadata() 

131 metadata2[mockKey] = "Sphere" 

132 self.task.run([metadata1, metadata2]) 

133 mockWorkhorse.assert_called_once_with( 

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

135 

136 def testAmbiguousRun(self): 

137 mockKey = "unitTestKey" 

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

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

140 metadata = TaskMetadata() 

141 metadata[mockKey + "1"] = 42 

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

143 with self.assertRaises(MetricComputationError): 

144 if self._takesScalarMetadata(self.task): 

145 self.task.run(metadata) 

146 else: 

147 self.task.run([metadata]) 

148 

149 def testPassThroughRun(self): 

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

151 side_effect=MetricComputationError): 

152 metadata1 = TaskMetadata() 

153 metadata1["unitTestKey"] = 42 

154 with self.assertRaises(MetricComputationError): 

155 if self._takesScalarMetadata(self.task): 

156 self.task.run(metadata1) 

157 else: 

158 self.task.run([metadata1]) 

159 

160 def testDimensionsOverride(self): 

161 config = self.task.config 

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

163 config.metadataDimensions = expectedDimensions 

164 

165 connections = config.connections.ConnectionsClass(config=config) 

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

167 expectedDimensions) 

168 self.assertIn(connections.metadata, 

169 connections.allConnections.values()) 

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

171 expectedDimensions) 

172 

173 

174class ApdbMetricTestCase(MetricTaskTestCase): 

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

176 

177 Notes 

178 ----- 

179 Subclasses must override 

180 `~lsst.verify.tasks.MetricTaskTestCase.makeTask` for the concrete task 

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

182 override `makeDbInfo`. 

183 """ 

184 

185 @classmethod 

186 def makeDbInfo(cls): 

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

188 

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

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

191 should create that input directly. 

192 

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

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

195 that use a different loader should override this method. 

196 """ 

197 return ApdbConfig() 

198 

199 def testValidRun(self): 

200 info = self.makeDbInfo() 

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

202 self.task.run([info]) 

203 mockWorkhorse.assert_called_once() 

204 

205 def testDataIdRun(self): 

206 info = self.makeDbInfo() 

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

208 dataId = {'visit': 42} 

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

210 mockWorkhorse.assert_called_once_with( 

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

212 

213 def testPassThroughRun(self): 

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

215 side_effect=MetricComputationError): 

216 info = self.makeDbInfo() 

217 with self.assertRaises(MetricComputationError): 

218 self.task.run([info])