Coverage for tests/test_inspectjob.py : 22%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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/>.
22from io import StringIO
23import re
24import unittest.mock
26import astropy.units as u
28from lsst.verify import Job, Metric, Measurement, ThresholdSpecification
29from lsst.verify.bin.inspectjob import inspect_job
32@unittest.mock.patch("sys.stdout", new_callable=StringIO)
33class InspectJobTestCase(unittest.TestCase):
35 @classmethod
36 def setUpClass(cls):
37 # Do not use re.DOTALL; some tests assume it's not set
38 cls.regex_flags = re.IGNORECASE
40 def setUp(self):
41 self.job = Job()
42 self.job.metrics.insert(Metric("foo.boringmetric", "",
43 u.percent,
44 tags=["redundant"]))
45 self.job.metrics.insert(Metric("foo.fancymetric", "",
46 u.meter,
47 tags=["vital"]))
48 self.job.measurements.insert(Measurement("foo.fancymetric",
49 2.0 * u.meter))
50 self.job.measurements.insert(Measurement("foo.fanciermetric",
51 3.5 * u.second))
52 self.job.measurements["foo.fanciermetric"].notes["fanciness"] \
53 = "moderate"
54 self.job.measurements.insert(Measurement("foo.fanciestmetric",
55 3.1415927 * u.kilogram))
56 self.job.meta["bar"] = "high"
57 self.job.meta["shape"] = "rotund"
58 self.job.specs.insert(ThresholdSpecification("utterly_ridiculous",
59 1e10 * u.meter,
60 ">"))
61 # MUST run inspect_job inside test case to capture output
63 def test_metrics(self, mock_stdout):
64 """Test that inspect_job only mentions metrics with measurements."
65 """
66 inspect_job(self.job)
67 self.assertNotIn("foo.boringmetric", mock_stdout.getvalue())
68 self.assertIn("foo.fancymetric", mock_stdout.getvalue())
69 self.assertIn("foo.fanciermetric", mock_stdout.getvalue())
70 self.assertIn("foo.fanciestmetric", mock_stdout.getvalue())
72 def _check_measurement(self, measurement, output):
73 # Test for metric name, followed by value and units
74 # None of the examples are dimensionless, so can ignore that case
75 regex = r"%s\W+?(?P<value>[\d.-]+ \w+)" % (measurement.metric_name)
76 match = re.search(regex, output, flags=self.regex_flags)
78 error = "Can't find %s and value on same row." \
79 % measurement.metric_name
80 self.assertIsNotNone(match, msg=error)
82 value = match.group("value")
83 try:
84 trailing = re.match(r"\d+\.(\d+)", value, flags=self.regex_flags)
85 decimals = len(trailing.group(1))
86 except TypeError:
87 decimals = 0
88 # Don't test # of decimal places; trailing zeros may be dropped
89 self.assertEqual(str(measurement.quantity.round(decimals)), value)
91 def test_measurements(self, mock_stdout):
92 """Test that inspect_job dumps measurements with and without metadata.
93 """
94 inspect_job(self.job)
95 output = mock_stdout.getvalue()
96 # MeasurementSet.values does not exist
97 for _, measurement in self.job.measurements.items():
98 self._check_measurement(measurement, output)
100 def test_measurement_metadata(self, mock_stdout):
101 """Test that inspect_job dumps measurement-level metadata on the same
102 line as their measurement.
103 """
104 inspect_job(self.job)
105 output = mock_stdout.getvalue()
106 for metric_name, measurement in self.job.measurements.items():
107 line = re.search("^.*%s.*$" % metric_name,
108 output,
109 flags=self.regex_flags | re.MULTILINE)
110 error = "Can't find measurement %s" % metric_name
111 self.assertIsNotNone(line, msg=error)
112 line = line.group()
114 for key in measurement.notes:
115 regex = r"(?P<keyname>[\w\.]+)\W+%s" % (measurement.notes[key])
116 match = re.search(regex, line, flags=self.regex_flags)
117 self.assertIsNotNone(match,
118 msg="Can't find metadata %s." % key)
119 reportedMetadataName = match.group('keyname')
120 fullMetadataName = "%s.%s" % (measurement.metric_name,
121 reportedMetadataName)
122 self.assertEqual(fullMetadataName, key)
124 def _check_metadata(self, key, value, output):
125 regex = r"%s.+%s" % (key, value)
126 match = re.search(regex, output, flags=self.regex_flags)
127 self.assertIsNotNone(match, msg="Can't find metadata %s" % key)
129 def test_top_metadata(self, mock_stdout):
130 """Test that inspect_job dumps top-level metadata.
131 """
132 inspect_job(self.job)
133 output = mock_stdout.getvalue()
134 for key, value in [("bar", "high"),
135 ("shape", "rotund")]:
136 self._check_metadata(key, value, output)
138 def test_specs(self, mock_stdout):
139 """Test that inspect_job does not dump specifications."
140 """
141 self.assertNotIn("utterly_ridiculous", mock_stdout.getvalue())
143 def test_empty(self, mock_stdout):
144 """Test that inspect_job can handle files with neither metrics nor metadata.
145 """
146 inspect_job(Job())
147 # No specific output expected, so test passes if inspect_job
148 # didn't raise.
150 def test_metadataonly(self, mock_stdout):
151 """Test that inspect_job can handle files with metadata but no metrics.
152 """
153 # Job and its components were not designed to support deletion, so
154 # create a new Job from scratch to ensure it's a valid object.
155 job = Job()
156 job.metrics.insert(Metric("foo.boringmetric", "",
157 u.percent,
158 tags=["redundant"]))
159 job.metrics.insert(Metric("foo.fancymetric", "",
160 u.meter,
161 tags=["vital"]))
162 job.meta["bar"] = "high"
163 job.meta["shape"] = "rotund"
164 job.specs.insert(ThresholdSpecification("utterly_ridiculous",
165 1e10 * u.meter,
166 ">"))
168 inspect_job(job)
169 output = mock_stdout.getvalue()
170 for key, value in [("bar", "high"),
171 ("shape", "rotund")]:
172 self._check_metadata(key, value, output)
174 def test_metricsonly(self, mock_stdout):
175 """Test that inspect_job can handle files with metrics but no metadata.
176 """
177 # Job and its components were not designed to support deletion, so
178 # create a new Job from scratch to ensure it's a valid object.
179 job = Job()
180 job.metrics.insert(Metric("foo.boringmetric", "",
181 u.percent,
182 tags=["redundant"]))
183 job.metrics.insert(Metric("foo.fancymetric", "",
184 u.meter,
185 tags=["vital"]))
186 job.measurements.insert(Measurement("foo.fancymetric",
187 2.0 * u.meter))
188 job.measurements.insert(Measurement("foo.fanciermetric",
189 3.5 * u.second))
190 job.measurements["foo.fanciermetric"].notes["fanciness"] = "moderate"
191 job.measurements.insert(Measurement("foo.fanciestmetric",
192 3.1415927 * u.kilogram))
194 inspect_job(job)
195 output = mock_stdout.getvalue()
196 # MeasurementSet.values does not exist
197 for _, measurement in job.measurements.items():
198 self._check_measurement(measurement, output)
201if __name__ == "__main__": 201 ↛ 202line 201 didn't jump to line 202, because the condition on line 201 was never true
202 unittest.main()