Coverage for python/lsst/verify/tasks/testUtils.py: 26%
97 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 02:10 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 02:10 -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/>.
22__all__ = ["MetadataMetricTestCase", "ApdbMetricTestCase"]
24import abc
26import unittest.mock
27from unittest.mock import patch
29import lsst.utils.tests
30from lsst.pipe.base import TaskMetadata
31from lsst.dax.apdb import ApdbConfig
33from lsst.verify.tasks import MetricComputationError
36class MetricTaskTestCase(lsst.utils.tests.TestCase, metaclass=abc.ABCMeta):
37 """Unit test base class for tests of `tasks.MetricTask`.
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.
49 This overridable method will be called during test setup.
51 Returns
52 -------
53 task : `lsst.verify.tasks.MetricTask`
54 A new MetricTask object to test.
55 """
57 task = None
58 """The ``MetricTask`` being tested by this object
59 (`tasks.MetricTask`).
61 This attribute is initialized automatically.
62 """
64 taskClass = None
65 """The type of `task` (`tasks.MetricTask`-type).
67 This attribute is initialized automatically.
68 """
70 def setUp(self):
71 """Setup common to all MetricTask tests.
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)
81 def testOutputDatasetName(self):
82 config = self.task.config
83 connections = config.connections.ConnectionsClass(config=config)
84 dataset = connections.measurement.name
86 self.assertTrue(dataset.startswith("metricvalue_"))
87 self.assertNotIn(".", dataset)
89 self.assertIn(config.connections.package, dataset)
90 self.assertIn(config.connections.metric, dataset)
92 def testConfigValidation(self):
93 config = self.task.config
94 config.connections.metric = "verify.DummyMetric"
95 with self.assertRaises(ValueError):
96 config.validate()
99class MetadataMetricTestCase(MetricTaskTestCase):
100 """Unit test base class for tests of `MetadataMetricTask`.
102 Notes
103 -----
104 Subclasses must override
105 `~lsst.verify.tasks.MetricTaskTestCase.makeTask` for the concrete task
106 being tested.
107 """
109 @staticmethod
110 def _takesScalarMetadata(task):
111 connections = task.config.connections.ConnectionsClass(config=task.config)
112 return not connections.metadata.multiple
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
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"]])
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])
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])
160 def testDimensionsOverride(self):
161 config = self.task.config
162 expectedDimensions = {"instrument", "visit"}
163 config.metadataDimensions = expectedDimensions
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)
174class ApdbMetricTestCase(MetricTaskTestCase):
175 """Unit test base class for tests of `ApdbMetricTask`.
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 """
185 @classmethod
186 def makeDbInfo(cls):
187 """Return an object that can be passed as input to an `ApdbMetricTask`.
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.
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()
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()
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})
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])