Coverage for tests/test_efdUtils.py: 37%
153 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-05 14:39 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-05 14:39 +0000
1# This file is part of summit_utils.
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"""Test cases for utils."""
24import unittest
25import lsst.utils.tests
26import astropy
27import pandas as pd
28import datetime
29import asyncio
30from astropy.time import Time
32from lsst.summit.utils.tmaUtils import TMAEvent, TMAState
33from lsst.summit.utils.efdUtils import (
34 getEfdData,
35 getMostRecentRowWithDataBefore,
36 makeEfdClient,
37 efdTimestampToAstropy,
38 astropyToEfdTimestamp,
39 clipDataToEvent,
40 # calcNextDay, # this is indirectly tested by test_getDayObsAsTimes()
41 getDayObsStartTime,
42 getDayObsEndTime,
43 getDayObsForTime,
44 getTopics,
45)
47from utils import getVcr
49HAS_EFD_CLIENT = True
50try:
51 import lsst_efd_client
52except ImportError:
53 HAS_EFD_CLIENT = False
55vcr = getVcr()
58@unittest.skipIf(not HAS_EFD_CLIENT, "No EFD client available")
59@vcr.use_cassette()
60class EfdUtilsTestCase(lsst.utils.tests.TestCase):
62 @classmethod
63 @vcr.use_cassette()
64 def setUpClass(cls):
65 try:
66 cls.client = makeEfdClient(testing=True)
67 except RuntimeError:
68 raise unittest.SkipTest("Could not instantiate an EFD client")
69 cls.dayObs = 20230531
70 # get a sample expRecord here to test expRecordToTimespan
71 cls.axisTopic = 'lsst.sal.MTMount.logevent_azimuthMotionState'
72 cls.timeSeriesTopic = 'lsst.sal.MTMount.azimuth'
73 cls.event = TMAEvent(
74 dayObs=20230531,
75 seqNum=27,
76 type=TMAState.TRACKING,
77 endReason=TMAState.SLEWING,
78 duration=0.47125244140625,
79 begin=Time(1685578353.2265284, scale='utc', format='unix'),
80 end=Time(1685578353.6977808, scale='utc', format='unix'),
81 blockInfos=None,
82 version=0,
83 _startRow=254,
84 _endRow=255,
85 )
87 @vcr.use_cassette()
88 def tearDown(self):
89 loop = asyncio.get_event_loop()
90 loop.run_until_complete(self.client.influx_client.close())
92 @vcr.use_cassette()
93 def test_makeEfdClient(self):
94 self.assertIsInstance(self.client, lsst_efd_client.efd_helper.EfdClient)
96 def test_getDayObsAsTimes(self):
97 """This tests getDayObsStartTime and getDayObsEndTime explicitly,
98 but the days we loop over are chosen to test calcNextDay() which is
99 called by getDayObsEndTime().
100 """
101 for dayObs in (
102 self.dayObs, # the nominal value
103 20200228, # day before end of Feb on a leap year
104 20200229, # end of Feb on a leap year
105 20210227, # day before end of Feb on a non-leap year
106 20200228, # end of Feb on a non-leap year
107 20200430, # end of a month with 30 days
108 20200530, # end of a month with 31 days
109 20201231, # year rollover
110 ):
111 dayStart = getDayObsStartTime(dayObs)
112 self.assertIsInstance(dayStart, astropy.time.Time)
114 dayEnd = getDayObsEndTime(dayObs)
115 self.assertIsInstance(dayStart, astropy.time.Time)
117 self.assertGreater(dayEnd, dayStart)
118 self.assertEqual(dayEnd.jd, dayStart.jd + 1)
120 @vcr.use_cassette()
121 def test_getTopics(self):
122 topics = getTopics(self.client, 'lsst.sal.MTMount*')
123 self.assertIsInstance(topics, list)
124 self.assertGreater(len(topics), 0)
126 topics = getTopics(self.client, '*fake.topics.does.not.exist*')
127 self.assertIsInstance(topics, list)
128 self.assertEqual(len(topics), 0)
130 # check we can find the mount with a preceding wildcard
131 topics = getTopics(self.client, '*mTmoUnt*')
132 self.assertIsInstance(topics, list)
133 self.assertGreater(len(topics), 0)
135 # check it fails if we don't allow case insensitivity
136 topics = getTopics(self.client, '*mTmoUnt*', caseSensitive=True)
137 self.assertIsInstance(topics, list)
138 self.assertEqual(len(topics), 0)
140 @vcr.use_cassette()
141 def test_getEfdData(self):
142 dayStart = getDayObsStartTime(self.dayObs)
143 dayEnd = getDayObsEndTime(self.dayObs)
144 oneDay = datetime.timedelta(hours=24)
145 # twelveHours = datetime.timedelta(hours=12)
147 # test the dayObs interface
148 dayObsData = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs)
149 self.assertIsInstance(dayObsData, pd.DataFrame)
151 # test the starttime interface
152 dayStartData = getEfdData(self.client, self.axisTopic, begin=dayStart, timespan=oneDay)
153 self.assertIsInstance(dayStartData, pd.DataFrame)
155 # check they're equal
156 self.assertTrue(dayObsData.equals(dayStartData))
158 # test the starttime interface with an endtime
159 dayEnd = getDayObsEndTime(self.dayObs)
160 dayStartEndData = getEfdData(self.client, self.axisTopic, begin=dayStart, end=dayEnd)
161 self.assertTrue(dayObsData.equals(dayStartEndData))
163 # test event
164 # note that here we're going to clip to an event and pad things, so
165 # we want to use the timeSeriesTopic not the states, so that there's
166 # plenty of rows to test the padding is actually working
167 eventData = getEfdData(self.client, self.timeSeriesTopic, event=self.event)
168 self.assertIsInstance(dayObsData, pd.DataFrame)
170 # test padding options
171 padded = getEfdData(self.client, self.timeSeriesTopic, event=self.event, prePadding=1, postPadding=2)
172 self.assertGreater(len(padded), len(eventData))
173 startTimeDiff = (efdTimestampToAstropy(eventData.iloc[0]['private_efdStamp']) -
174 efdTimestampToAstropy(padded.iloc[0]['private_efdStamp']))
175 endTimeDiff = (efdTimestampToAstropy(padded.iloc[-1]['private_efdStamp']) -
176 efdTimestampToAstropy(eventData.iloc[-1]['private_efdStamp']))
178 self.assertGreater(startTimeDiff.sec, 0)
179 self.assertLess(startTimeDiff.sec, 1.1) # padding isn't super exact, so give a little wiggle room
180 self.assertGreater(endTimeDiff.sec, 0)
181 self.assertLess(endTimeDiff.sec, 2.1) # padding isn't super exact, so give a little wiggle room
183 with self.assertRaises(ValueError):
184 # not enough info to constrain
185 _ = getEfdData(self.client, self.axisTopic)
186 # dayObs supplied and a start time is not allowed
187 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, begin=dayStart)
188 # dayObs supplied and a stop time is not allowed
189 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, end=dayEnd)
190 # dayObs supplied and timespan is not allowed
191 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, timespan=oneDay)
192 # being alone is not allowed
193 _ = getEfdData(self.client, self.axisTopic, begin=self.dayObs)
194 # good query, except the topic doesn't exist
195 _ = getEfdData(self.client, 'badTopic', begin=dayStart, end=dayEnd)
197 @vcr.use_cassette()
198 def test_getMostRecentRowWithDataBefore(self):
199 time = Time(1687845854.736784, scale='utc', format='unix')
200 rowData = getMostRecentRowWithDataBefore(self.client,
201 "lsst.sal.MTM1M3.logevent_forceActuatorState",
202 time)
203 self.assertIsInstance(rowData, pd.Series)
205 stateTime = efdTimestampToAstropy(rowData['private_efdStamp'])
206 self.assertLess(stateTime, time)
208 def test_efdTimestampToAstropy(self):
209 time = efdTimestampToAstropy(1687845854.736784)
210 self.assertIsInstance(time, astropy.time.Time)
211 return
213 def test_astropyToEfdTimestamp(self):
214 time = Time(1687845854.736784, scale='utc', format='unix')
215 efdTimestamp = astropyToEfdTimestamp(time)
216 self.assertIsInstance(efdTimestamp, float)
217 return
219 @vcr.use_cassette()
220 def test_clipDataToEvent(self):
221 # get 10 mins of data either side of the event we'll clip to
222 duration = datetime.timedelta(seconds=10*60)
223 queryBegin = self.event.begin - duration
224 queryEnd = self.event.end + duration
225 dayObsData = getEfdData(self.client, 'lsst.sal.MTMount.azimuth', begin=queryBegin, end=queryEnd)
227 # clip the data, and check it's shorter, non-zero, and falls in the
228 # right time range
229 clippedData = clipDataToEvent(dayObsData, self.event)
231 self.assertIsInstance(clippedData, pd.DataFrame)
232 self.assertGreater(len(clippedData), 0)
233 self.assertLess(len(clippedData), len(dayObsData))
235 dataStart = efdTimestampToAstropy(clippedData.iloc[0]['private_efdStamp'])
236 dataEnd = efdTimestampToAstropy(clippedData.iloc[-1]['private_efdStamp'])
238 self.assertGreaterEqual(dataStart, self.event.begin)
239 self.assertLessEqual(dataEnd, self.event.end)
241 # test the pre/post padding options
242 clippedPaddedData = clipDataToEvent(dayObsData, self.event, prePadding=1, postPadding=2)
243 self.assertIsInstance(clippedPaddedData, pd.DataFrame)
244 self.assertGreater(len(clippedPaddedData), 0)
245 self.assertLess(len(clippedPaddedData), len(dayObsData))
246 self.assertGreater(len(clippedPaddedData), len(clippedData))
248 paddedDataStart = efdTimestampToAstropy(clippedPaddedData.iloc[0]['private_efdStamp'])
249 paddedDataEnd = efdTimestampToAstropy(clippedPaddedData.iloc[-1]['private_efdStamp'])
250 self.assertLessEqual(paddedDataStart, dataStart)
251 self.assertGreaterEqual(paddedDataEnd, dataEnd)
253 # Get the minimum and maximum timestamps before padding
254 startTimeUnpadded = clippedData['private_efdStamp'].min()
255 endTimeUnpadded = clippedData['private_efdStamp'].max()
257 # Get the minimum and maximum timestamps after padding
258 startTimePadded = clippedPaddedData['private_efdStamp'].min()
259 endTimePadded = clippedPaddedData['private_efdStamp'].max()
261 # Check that the difference between the min times and max times is
262 # approximately equal to the padding. Not exact as data sampling is
263 # not infinite.
264 self.assertAlmostEqual(startTimeUnpadded - startTimePadded, 1, delta=0.1)
265 self.assertAlmostEqual(endTimePadded - endTimeUnpadded, 2, delta=0.1)
266 return
268 def test_getDayObsForTime(self):
269 pydate = datetime.datetime(2023, 2, 5, 13, 30, 1)
270 time = Time(pydate)
271 dayObs = getDayObsForTime(time)
272 self.assertEqual(dayObs, 20230205)
274 pydate = datetime.datetime(2023, 2, 5, 11, 30, 1)
275 time = Time(pydate)
276 dayObs = getDayObsForTime(time)
277 self.assertEqual(dayObs, 20230204)
278 return
281class TestMemory(lsst.utils.tests.MemoryTestCase):
282 pass
285def setup_module(module):
286 lsst.utils.tests.init()
289if __name__ == "__main__": 289 ↛ 290line 289 didn't jump to line 290, because the condition on line 289 was never true
290 lsst.utils.tests.init()
291 unittest.main()