Coverage for python/lsst/verify/tasks/testUtils.py: 30%
98 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-29 02:34 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-29 02:34 -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
30from lsst.pipe.base import TaskMetadata
31from lsst.dax.apdb import ApdbConfig
33import lsst.verify.gen2tasks.testUtils as gen2Utils
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(gen2Utils.MetricTaskTestCase, MetricTaskTestCase):
101 """Unit test base class for tests of `MetadataMetricTask`.
103 Notes
104 -----
105 Subclasses must override
106 `~lsst.verify.gen2tasks.MetricTaskTestCase.makeTask` for the concrete task
107 being tested.
108 """
110 @staticmethod
111 def _takesScalarMetadata(task):
112 return task.areInputDatasetsScalar(task.config)['metadata']
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 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"]])
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])
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])
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(gen2Utils.MetricTaskTestCase, MetricTaskTestCase):
176 """Unit test base class for tests of `ApdbMetricTask`.
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 """
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])