Coverage for python/lsst/verify/tasks/testUtils.py: 29%
116 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-14 02:06 -0700
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-14 02:06 -0700
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/>.
22__all__ = ["MetadataMetricTestCase", "ApdbMetricTestCase"]
24import abc
26import unittest.mock
27from unittest.mock import patch
29import lsst.utils.tests
30import lsst.pex.config as pexConfig
31from lsst.pipe.base import TaskMetadata
32from lsst.dax.apdb import ApdbConfig
34from lsst.verify.tasks import MetricComputationError
37class MetricTaskTestCase(lsst.utils.tests.TestCase, metaclass=abc.ABCMeta):
38 """Unit test base class for tests of `tasks.MetricTask`.
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.
50 This overridable method will be called during test setup.
52 Returns
53 -------
54 task : `lsst.verify.tasks.MetricTask`
55 A new MetricTask object to test.
56 """
58 task = None
59 """The ``MetricTask`` being tested by this object
60 (`tasks.MetricTask`).
62 This attribute is initialized automatically.
63 """
65 taskClass = None
66 """The type of `task` (`tasks.MetricTask`-type).
68 This attribute is initialized automatically.
69 """
71 def setUp(self):
72 """Setup common to all MetricTask tests.
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)
82 def testOutputDatasetName(self):
83 config = self.task.config
84 connections = config.connections.ConnectionsClass(config=config)
85 dataset = connections.measurement.name
87 self.assertTrue(dataset.startswith("metricvalue_"))
88 self.assertNotIn(".", dataset)
90 self.assertIn(config.connections.package, dataset)
91 self.assertIn(config.connections.metric, dataset)
93 def testConfigValidation(self):
94 config = self.task.config
95 config.connections.metric = "verify.DummyMetric"
96 with self.assertRaises(ValueError):
97 config.validate()
100class MetadataMetricTestCase(MetricTaskTestCase):
101 """Unit test base class for tests of `MetadataMetricTask`.
103 Notes
104 -----
105 Subclasses must override
106 `~lsst.verify.tasks.MetricTaskTestCase.makeTask` for the concrete task
107 being tested.
108 """
110 @staticmethod
111 def _takesScalarMetadata(task):
112 connections = task.config.connections.ConnectionsClass(config=task.config)
113 return not connections.metadata.multiple
115 def testValidRun(self):
116 """Test how run delegates to the abstract methods.
117 """
118 mockKey = "unitTestKey"
119 with patch.object(self.task, "getInputMetadataKeys",
120 return_value={"unused": mockKey}), \
121 patch.object(self.task, "makeMeasurement") as mockWorkhorse:
122 if self._takesScalarMetadata(self.task):
123 metadata1 = TaskMetadata()
124 metadata1[mockKey] = 42
126 self.task.run(metadata1)
127 mockWorkhorse.assert_called_once_with({"unused": 42})
128 else:
129 metadata1 = TaskMetadata()
130 metadata1[mockKey] = 42
131 metadata2 = TaskMetadata()
132 metadata2[mockKey] = "Sphere"
133 self.task.run([metadata1, metadata2])
134 mockWorkhorse.assert_called_once_with(
135 [{"unused": value} for value in [42, "Sphere"]])
137 def testAmbiguousRun(self):
138 mockKey = "unitTestKey"
139 with patch.object(self.task, "getInputMetadataKeys",
140 return_value={"unused": mockKey}):
141 metadata = TaskMetadata()
142 metadata[mockKey + "1"] = 42
143 metadata[mockKey + "2"] = "Sphere"
144 with self.assertRaises(MetricComputationError):
145 if self._takesScalarMetadata(self.task):
146 self.task.run(metadata)
147 else:
148 self.task.run([metadata])
150 def testPassThroughRun(self):
151 with patch.object(self.task, "makeMeasurement",
152 side_effect=MetricComputationError):
153 metadata1 = TaskMetadata()
154 metadata1["unitTestKey"] = 42
155 with self.assertRaises(MetricComputationError):
156 if self._takesScalarMetadata(self.task):
157 self.task.run(metadata1)
158 else:
159 self.task.run([metadata1])
161 def testDimensionsOverride(self):
162 config = self.task.config
163 expectedDimensions = {"instrument", "visit"}
164 config.metadataDimensions = expectedDimensions
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)
175class ApdbMetricTestCase(MetricTaskTestCase):
176 """Unit test base class for tests of `ApdbMetricTask`.
178 Notes
179 -----
180 Subclasses must override
181 `~lsst.verify.tasks.MetricTaskTestCase.makeTask` for the concrete task
182 being tested. Subclasses that use a custom DbLoader should also
183 override `makeDbInfo`.
184 """
186 @classmethod
187 def makeDbInfo(cls):
188 """Return an object that can be passed as input to an `ApdbMetricTask`.
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.
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()
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()
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})
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])
221 # TODO: remove on DM-43419
222 def testConfigApdbRead(self):
223 config = self.taskClass.ConfigClass()
224 with self.assertWarns(FutureWarning):
225 config.doReadMarker = True
226 config.freeze()
227 config.validate()
229 # TODO: remove on DM-43419
230 def testConfigApdbFileOk(self):
231 config = self.taskClass.ConfigClass()
232 config.doReadMarker = False
233 config.apdb_config_url = "some/file/path.yaml"
234 config.freeze()
235 config.validate()
237 # TODO: remove on DM-43419
238 def testConfigApdbFileInvalid(self):
239 config = self.taskClass.ConfigClass()
240 config.doReadMarker = False
241 # Don't set apdb_config_url
242 config.freeze()
243 with self.assertRaises(pexConfig.FieldValidationError):
244 config.validate()