Coverage for tests/test_efdUtils.py: 43%

135 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-17 11:44 +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 os 

26import lsst.utils.tests 

27import astropy 

28import pandas as pd 

29import datetime 

30import asyncio 

31from astropy.time import Time 

32import vcr 

33 

34from lsst.utils import getPackageDir 

35from lsst.summit.utils.tmaUtils import TMAEvent, TMAState 

36 

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) 

50 

51HAS_EFD_CLIENT = True 

52try: 

53 import lsst_efd_client 

54except ImportError: 

55 HAS_EFD_CLIENT = False 

56 

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) 

70 

71 

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

73@safe_vcr.use_cassette() 

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

75 

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 ) 

100 

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

105 

106 @safe_vcr.use_cassette() 

107 def test_makeEfdClient(self): 

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

109 

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) 

127 

128 dayEnd = getDayObsEndTime(dayObs) 

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

130 

131 self.assertGreater(dayEnd, dayStart) 

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

133 

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) 

139 

140 subTopics = getSubTopics(self.client, 'fake.topics.does.not.exist') 

141 self.assertIsInstance(subTopics, list) 

142 self.assertEqual(len(subTopics), 0) 

143 

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) 

150 

151 # test the dayObs interface 

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

153 self.assertIsInstance(dayObsData, pd.DataFrame) 

154 

155 # test the starttime interface 

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

157 self.assertIsInstance(dayStartData, pd.DataFrame) 

158 

159 # check they're equal 

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

161 

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

166 

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) 

173 

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

181 

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 

186 

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) 

200 

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) 

208 

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

210 self.assertLess(stateTime, time) 

211 

212 def test_efdTimestampToAstropy(self): 

213 time = efdTimestampToAstropy(1687845854.736784) 

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

215 return 

216 

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 

222 

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) 

230 

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) 

234 

235 self.assertIsInstance(clippedData, pd.DataFrame) 

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

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

238 

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

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

241 

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

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

244 return 

245 

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) 

251 

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 

257 

258 

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

260 pass 

261 

262 

263def setup_module(module): 

264 lsst.utils.tests.init() 

265 

266 

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