Coverage for tests/test_efdUtils.py: 43%
135 statements
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-15 10:03 +0000
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-15 10:03 +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 os
26import lsst.utils.tests
27import astropy
28import pandas as pd
29import datetime
30import asyncio
31from astropy.time import Time
32import vcr
34from lsst.utils import getPackageDir
35from lsst.summit.utils.tmaUtils import TMAEvent, TMAState
37from lsst.summit.utils.efdUtils import (
38 getEfdData,
39 getMostRecentRowWithDataBefore,
40 makeEfdClient,
41 efdTimestampToAstropy,
42 astropyToEfdTimestamp,
43 clipDataToEvent,
44 # calcNextDay, # this is indirectly tested by test_getDayObsAsTimes()
45 getDayObsStartTime,
46 getDayObsEndTime,
47 getDayObsForTime,
48 getSubTopics,
49)
51HAS_EFD_CLIENT = True
52try:
53 import lsst_efd_client
54except ImportError:
55 HAS_EFD_CLIENT = False
57# Use record_mode="none" to run tests for normal operation. To update files or
58# generate new ones, make sure you have a working connection to the EFD at all
59# the relevant sites, and temporarily run with mode="all" via *both*
60# python/pytest *and* with scons, as these generate slightly different HTTP
61# requests for some reason. Also make sure to do all this at both the summit
62# and USDF. The TTS is explicitly skipped and does not need to follow this
63# procedure.
64packageDir = getPackageDir('summit_utils')
65safe_vcr = vcr.VCR(
66 record_mode="none",
67 cassette_library_dir=os.path.join(packageDir, "tests", "data", "cassettes"),
68 path_transformer=vcr.VCR.ensure_suffix(".yaml"),
69)
72@unittest.skipIf(not HAS_EFD_CLIENT, "No EFD client available")
73@safe_vcr.use_cassette()
74class EfdUtilsTestCase(lsst.utils.tests.TestCase):
76 @classmethod
77 @safe_vcr.use_cassette()
78 def setUpClass(cls):
79 try:
80 cls.client = makeEfdClient(testing=True)
81 except RuntimeError:
82 raise unittest.SkipTest("Could not instantiate an EFD client")
83 cls.dayObs = 20230531
84 # get a sample expRecord here to test expRecordToTimespan
85 cls.axisTopic = 'lsst.sal.MTMount.logevent_azimuthMotionState'
86 cls.timeSeriesTopic = 'lsst.sal.MTMount.azimuth'
87 cls.event = TMAEvent(
88 dayObs=20230531,
89 seqNum=27,
90 type=TMAState.TRACKING,
91 endReason=TMAState.SLEWING,
92 duration=0.47125244140625,
93 begin=Time(1685578353.2265284, scale='utc', format='unix'),
94 end=Time(1685578353.6977808, scale='utc', format='unix'),
95 blockInfo=None,
96 version=0,
97 _startRow=254,
98 _endRow=255,
99 )
101 @safe_vcr.use_cassette()
102 def tearDown(self):
103 loop = asyncio.get_event_loop()
104 loop.run_until_complete(self.client.influx_client.close())
106 @safe_vcr.use_cassette()
107 def test_makeEfdClient(self):
108 self.assertIsInstance(self.client, lsst_efd_client.efd_helper.EfdClient)
110 def test_getDayObsAsTimes(self):
111 """This tests getDayObsStartTime and getDayObsEndTime explicitly,
112 but the days we loop over are chosen to test calcNextDay() which is
113 called by getDayObsEndTime().
114 """
115 for dayObs in (
116 self.dayObs, # the nominal value
117 20200228, # day before end of Feb on a leap year
118 20200229, # end of Feb on a leap year
119 20210227, # day before end of Feb on a non-leap year
120 20200228, # end of Feb on a non-leap year
121 20200430, # end of a month with 30 days
122 20200530, # end of a month with 31 days
123 20201231, # year rollover
124 ):
125 dayStart = getDayObsStartTime(dayObs)
126 self.assertIsInstance(dayStart, astropy.time.Time)
128 dayEnd = getDayObsEndTime(dayObs)
129 self.assertIsInstance(dayStart, astropy.time.Time)
131 self.assertGreater(dayEnd, dayStart)
132 self.assertEqual(dayEnd.jd, dayStart.jd + 1)
134 @safe_vcr.use_cassette()
135 def test_getSubTopics(self):
136 subTopics = getSubTopics(self.client, 'lsst.sal.MTMount')
137 self.assertIsInstance(subTopics, list)
138 self.assertGreater(len(subTopics), 0)
140 subTopics = getSubTopics(self.client, 'fake.topics.does.not.exist')
141 self.assertIsInstance(subTopics, list)
142 self.assertEqual(len(subTopics), 0)
144 @safe_vcr.use_cassette()
145 def test_getEfdData(self):
146 dayStart = getDayObsStartTime(self.dayObs)
147 dayEnd = getDayObsEndTime(self.dayObs)
148 oneDay = datetime.timedelta(hours=24)
149 # twelveHours = datetime.timedelta(hours=12)
151 # test the dayObs interface
152 dayObsData = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs)
153 self.assertIsInstance(dayObsData, pd.DataFrame)
155 # test the starttime interface
156 dayStartData = getEfdData(self.client, self.axisTopic, begin=dayStart, timespan=oneDay)
157 self.assertIsInstance(dayStartData, pd.DataFrame)
159 # check they're equal
160 self.assertTrue(dayObsData.equals(dayStartData))
162 # test the starttime interface with an endtime
163 dayEnd = getDayObsEndTime(self.dayObs)
164 dayStartEndData = getEfdData(self.client, self.axisTopic, begin=dayStart, end=dayEnd)
165 self.assertTrue(dayObsData.equals(dayStartEndData))
167 # test event
168 # note that here we're going to clip to an event and pad things, so
169 # we want to use the timeSeriesTopic not the states, so that there's
170 # plenty of rows to test the padding is actually working
171 eventData = getEfdData(self.client, self.timeSeriesTopic, event=self.event)
172 self.assertIsInstance(dayObsData, pd.DataFrame)
174 # test padding options
175 padded = getEfdData(self.client, self.timeSeriesTopic, event=self.event, prePadding=1, postPadding=2)
176 self.assertGreater(len(padded), len(eventData))
177 startTimeDiff = (efdTimestampToAstropy(eventData.iloc[0]['private_efdStamp']) -
178 efdTimestampToAstropy(padded.iloc[0]['private_efdStamp']))
179 endTimeDiff = (efdTimestampToAstropy(padded.iloc[-1]['private_efdStamp']) -
180 efdTimestampToAstropy(eventData.iloc[-1]['private_efdStamp']))
182 self.assertGreater(startTimeDiff.sec, 0)
183 self.assertLess(startTimeDiff.sec, 1.1) # padding isn't super exact, so give a little wiggle room
184 self.assertGreater(endTimeDiff.sec, 0)
185 self.assertLess(endTimeDiff.sec, 2.1) # padding isn't super exact, so give a little wiggle room
187 with self.assertRaises(ValueError):
188 # not enough info to constrain
189 _ = getEfdData(self.client, self.axisTopic)
190 # dayObs supplied and a start time is not allowed
191 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, begin=dayStart)
192 # dayObs supplied and a stop time is not allowed
193 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, end=dayEnd)
194 # dayObs supplied and timespan is not allowed
195 _ = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs, timespan=oneDay)
196 # being alone is not allowed
197 _ = getEfdData(self.client, self.axisTopic, begin=self.dayObs)
198 # good query, except the topic doesn't exist
199 _ = getEfdData(self.client, 'badTopic', begin=dayStart, end=dayEnd)
201 @safe_vcr.use_cassette()
202 def test_getMostRecentRowWithDataBefore(self):
203 time = Time(1687845854.736784, scale='utc', format='unix')
204 rowData = getMostRecentRowWithDataBefore(self.client,
205 "lsst.sal.MTM1M3.logevent_forceActuatorState",
206 time)
207 self.assertIsInstance(rowData, pd.Series)
209 stateTime = efdTimestampToAstropy(rowData['private_efdStamp'])
210 self.assertLess(stateTime, time)
212 def test_efdTimestampToAstropy(self):
213 time = efdTimestampToAstropy(1687845854.736784)
214 self.assertIsInstance(time, astropy.time.Time)
215 return
217 def test_astropyToEfdTimestamp(self):
218 time = Time(1687845854.736784, scale='utc', format='unix')
219 efdTimestamp = astropyToEfdTimestamp(time)
220 self.assertIsInstance(efdTimestamp, float)
221 return
223 @safe_vcr.use_cassette()
224 def test_clipDataToEvent(self):
225 # get 10 mins of data either side of the event we'll clip to
226 duration = datetime.timedelta(seconds=10*60)
227 queryBegin = self.event.begin - duration
228 queryEnd = self.event.end + duration
229 dayObsData = getEfdData(self.client, 'lsst.sal.MTMount.azimuth', begin=queryBegin, end=queryEnd)
231 # clip the data, and check it's shorter, non-zero, and falls in the
232 # right time range
233 clippedData = clipDataToEvent(dayObsData, self.event)
235 self.assertIsInstance(clippedData, pd.DataFrame)
236 self.assertGreater(len(clippedData), 0)
237 self.assertLess(len(clippedData), len(dayObsData))
239 dataStart = efdTimestampToAstropy(clippedData.iloc[0]['private_efdStamp'])
240 dataEnd = efdTimestampToAstropy(clippedData.iloc[-1]['private_efdStamp'])
242 self.assertGreaterEqual(dataStart, self.event.begin)
243 self.assertLessEqual(dataEnd, self.event.end)
244 return
246 def test_getDayObsForTime(self):
247 pydate = datetime.datetime(2023, 2, 5, 13, 30, 1)
248 time = Time(pydate)
249 dayObs = getDayObsForTime(time)
250 self.assertEqual(dayObs, 20230205)
252 pydate = datetime.datetime(2023, 2, 5, 11, 30, 1)
253 time = Time(pydate)
254 dayObs = getDayObsForTime(time)
255 self.assertEqual(dayObs, 20230204)
256 return
259class TestMemory(lsst.utils.tests.MemoryTestCase):
260 pass
263def setup_module(module):
264 lsst.utils.tests.init()
267if __name__ == "__main__": 267 ↛ 268line 267 didn't jump to line 268, because the condition on line 267 was never true
268 lsst.utils.tests.init()
269 unittest.main()