Coverage for tests/test_efdUtils.py: 40%

138 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-18 14:14 +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/>. 

21 

22"""Test cases for utils.""" 

23 

24import unittest 

25import lsst.utils.tests 

26import astropy 

27import pandas as pd 

28import datetime 

29import asyncio 

30from astropy.time import Time 

31 

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) 

46 

47from utils import getVcr 

48 

49HAS_EFD_CLIENT = True 

50try: 

51 import lsst_efd_client 

52except ImportError: 

53 HAS_EFD_CLIENT = False 

54 

55vcr = getVcr() 

56 

57 

58@unittest.skipIf(not HAS_EFD_CLIENT, "No EFD client available") 

59@vcr.use_cassette() 

60class EfdUtilsTestCase(lsst.utils.tests.TestCase): 

61 

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 ) 

86 

87 @vcr.use_cassette() 

88 def tearDown(self): 

89 loop = asyncio.get_event_loop() 

90 loop.run_until_complete(self.client.influx_client.close()) 

91 

92 @vcr.use_cassette() 

93 def test_makeEfdClient(self): 

94 self.assertIsInstance(self.client, lsst_efd_client.efd_helper.EfdClient) 

95 

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) 

113 

114 dayEnd = getDayObsEndTime(dayObs) 

115 self.assertIsInstance(dayStart, astropy.time.Time) 

116 

117 self.assertGreater(dayEnd, dayStart) 

118 self.assertEqual(dayEnd.jd, dayStart.jd + 1) 

119 

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) 

125 

126 topics = getTopics(self.client, '*fake.topics.does.not.exist*') 

127 self.assertIsInstance(topics, list) 

128 self.assertEqual(len(topics), 0) 

129 

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) 

134 

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) 

139 

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) 

146 

147 # test the dayObs interface 

148 dayObsData = getEfdData(self.client, self.axisTopic, dayObs=self.dayObs) 

149 self.assertIsInstance(dayObsData, pd.DataFrame) 

150 

151 # test the starttime interface 

152 dayStartData = getEfdData(self.client, self.axisTopic, begin=dayStart, timespan=oneDay) 

153 self.assertIsInstance(dayStartData, pd.DataFrame) 

154 

155 # check they're equal 

156 self.assertTrue(dayObsData.equals(dayStartData)) 

157 

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)) 

162 

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) 

169 

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'])) 

177 

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 

182 

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) 

196 

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) 

204 

205 stateTime = efdTimestampToAstropy(rowData['private_efdStamp']) 

206 self.assertLess(stateTime, time) 

207 

208 def test_efdTimestampToAstropy(self): 

209 time = efdTimestampToAstropy(1687845854.736784) 

210 self.assertIsInstance(time, astropy.time.Time) 

211 return 

212 

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 

218 

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) 

226 

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) 

230 

231 self.assertIsInstance(clippedData, pd.DataFrame) 

232 self.assertGreater(len(clippedData), 0) 

233 self.assertLess(len(clippedData), len(dayObsData)) 

234 

235 dataStart = efdTimestampToAstropy(clippedData.iloc[0]['private_efdStamp']) 

236 dataEnd = efdTimestampToAstropy(clippedData.iloc[-1]['private_efdStamp']) 

237 

238 self.assertGreaterEqual(dataStart, self.event.begin) 

239 self.assertLessEqual(dataEnd, self.event.end) 

240 return 

241 

242 def test_getDayObsForTime(self): 

243 pydate = datetime.datetime(2023, 2, 5, 13, 30, 1) 

244 time = Time(pydate) 

245 dayObs = getDayObsForTime(time) 

246 self.assertEqual(dayObs, 20230205) 

247 

248 pydate = datetime.datetime(2023, 2, 5, 11, 30, 1) 

249 time = Time(pydate) 

250 dayObs = getDayObsForTime(time) 

251 self.assertEqual(dayObs, 20230204) 

252 return 

253 

254 

255class TestMemory(lsst.utils.tests.MemoryTestCase): 

256 pass 

257 

258 

259def setup_module(module): 

260 lsst.utils.tests.init() 

261 

262 

263if __name__ == "__main__": 263 ↛ 264line 263 didn't jump to line 264, because the condition on line 263 was never true

264 lsst.utils.tests.init() 

265 unittest.main()