Coverage for tests / test_nightReport.py: 24%

140 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-21 11: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/>. 

21 

22import datetime 

23import itertools 

24import os 

25import tempfile 

26import unittest 

27from unittest import mock 

28 

29import matplotlib as mpl 

30from astro_metadata_translator import ObservationInfo 

31from numpy.random import shuffle 

32 

33import lsst.utils.tests 

34 

35mpl.use("Agg") 

36 

37import lsst.summit.utils.butlerUtils as butlerUtils # noqa: E402 

38from lsst.summit.utils.nightReport import ColorAndMarker, NightReport # noqa: E402 

39from lsst.summit.utils.utils import getSite # noqa: E402 

40 

41 

42class NightReportTestCase(lsst.utils.tests.TestCase): 

43 @classmethod 

44 def setUpClass(cls): 

45 try: 

46 if getSite() == "jenkins": 

47 raise unittest.SkipTest("Skip running butler-driven tests in Jenkins.") 

48 cls.butler = butlerUtils.makeDefaultLatissButler() 

49 except FileNotFoundError: 

50 raise unittest.SkipTest("Skipping tests that require the LATISS butler repo.") 

51 

52 cls.dayObs = 20200314 # has 377 images and data also exists on the TTS & summit 

53 

54 # Do the init in setUpClass because this takes about 35s for 20200314 

55 cls.report = NightReport(cls.butler, cls.dayObs) 

56 # number of images isn't necessarily the same as the number for the 

57 # the dayObs in the registry becacuse of the test stands/summit 

58 # having partial data, so get the number of images from the length 

59 # of the scraped data. Not ideal, but best that can be done due to 

60 # only having partial days in the test datasets. 

61 cls.nImages = len(cls.report.data.keys()) 

62 cls.seqNums = list(cls.report.data.keys()) 

63 

64 def test_saveAndLoad(self): 

65 """Test that a NightReport can save itself, and be loaded back.""" 

66 writeDir = tempfile.mkdtemp() 

67 saveFile = os.path.join(writeDir, f"testNightReport_{self.dayObs}.pickle") 

68 self.report.save(saveFile) 

69 self.assertTrue(os.path.exists(saveFile)) 

70 

71 loaded = NightReport(self.butler, self.dayObs, saveFile) 

72 self.assertIsInstance(loaded, lsst.summit.utils.nightReport.NightReport) 

73 self.assertGreaterEqual(len(loaded.data), 1) 

74 self.assertEqual(loaded.dayObs, self.dayObs) 

75 

76 # TODO: add a self.assertRaises on a mismatched dayObs 

77 

78 def test_getSortedData(self): 

79 """Test the _getSortedData returns the seqNums in order.""" 

80 shuffledKeys = list(self.report.data.keys()) 

81 shuffle(shuffledKeys) 

82 shuffledData = {k: self.report.data[k] for k in shuffledKeys} 

83 

84 sortedData = self.report._getSortedData(shuffledData) 

85 sortedKeys = sorted(list(sortedData.keys())) 

86 self.assertEqual(sortedKeys, list(self.report.data.keys())) 

87 return 

88 

89 def test_getExpRecordDictForDayObs(self): 

90 """Test getExpRecordDictForDayObs. 

91 

92 Test it returns a dict of dicts, keyed by integer seqNums. 

93 """ 

94 expRecDict = self.report.getExpRecordDictForDayObs(self.dayObs) 

95 self.assertIsInstance(expRecDict, dict) 

96 self.assertGreaterEqual(len(expRecDict), 1) 

97 

98 # check all the keys are ints 

99 seqNums = list(expRecDict.keys()) 

100 self.assertTrue(all(isinstance(s, int) for s in seqNums)) 

101 

102 # check all the values are dicts 

103 self.assertTrue(all(isinstance(expRecDict[s], dict) for s in seqNums)) 

104 return 

105 

106 def test_getObsInfoAndMetadataForSeqNum(self): 

107 """Test that getObsInfoAndMetadataForSeqNum returns the correct 

108 types. 

109 """ 

110 seqNum = self.seqNums[0] 

111 obsInfo, md = self.report.getObsInfoAndMetadataForSeqNum(seqNum) 

112 self.assertIsInstance(obsInfo, ObservationInfo) 

113 self.assertIsInstance(md, dict) 

114 return 

115 

116 def test_rebuild(self): 

117 """Test that rebuild does nothing, as no data will be being added. 

118 

119 NB Do not call full=True on this, as it will double the length of the 

120 tests and they're already extremely slow. 

121 """ 

122 lenBefore = len(self.report.data) 

123 self.report.rebuild() 

124 self.assertEqual(len(self.report.data), lenBefore) 

125 return 

126 

127 def test_getExposureMidpoint(self): 

128 """Test the exposure midpoint calculation""" 

129 # we would like a non-zero exptime exposure really 

130 seqNumToUse = 0 

131 for seqNum in self.report.data.keys(): 

132 expTime = self.report.data[seqNum]["exposure_time"] 

133 if expTime > 0: 

134 seqNumToUse = seqNum 

135 break 

136 

137 midPoint = self.report.getExposureMidpoint(seqNumToUse) 

138 record = self.report.data[seqNumToUse] 

139 

140 if expTime == 0: 

141 self.assertGreaterEqual(midPoint, record["datetime_begin"].to_datetime()) 

142 self.assertLessEqual(midPoint, record["datetime_end"].to_datetime()) 

143 else: 

144 self.assertGreater(midPoint, record["datetime_begin"].to_datetime()) 

145 self.assertLess(midPoint, record["datetime_end"].to_datetime()) 

146 return 

147 

148 def test_getTimeDeltas(self): 

149 """Test the time delta calculation returns a dict.""" 

150 dts = self.report.getTimeDeltas() 

151 self.assertIsInstance(dts, dict) 

152 return 

153 

154 def test_makeStarColorAndMarkerMap(self): 

155 """Test the color map maker returns a dict of ColorAndMarker 

156 objects. 

157 """ 

158 cMap = self.report.makeStarColorAndMarkerMap(self.report.stars) 

159 self.assertEqual(len(cMap), len(self.report.stars)) 

160 self.assertIsInstance(cMap, dict) 

161 values = list(cMap.values()) 

162 self.assertTrue(all(isinstance(value, ColorAndMarker) for value in values)) 

163 return 

164 

165 def test_printObsTable(self): 

166 """Test that a the printObsTable() method prints out the correct 

167 number of lines. 

168 """ 

169 with mock.patch("sys.stdout") as fake_stdout: 

170 self.report.printObsTable() 

171 

172 # newline for each row plus header line, plus the line with dashes 

173 self.assertEqual(len(fake_stdout.mock_calls), 2 * (self.nImages + 2)) 

174 

175 def test_plotPerObjectAirMass(self): 

176 """Test that a the per-object airmass plots runs.""" 

177 # We assume matplotlib is making plots, so just check that these 

178 # don't crash. 

179 

180 # Default plotting: 

181 self.report.plotPerObjectAirMass() 

182 # plot with only one object as a str not a list of str 

183 self.report.plotPerObjectAirMass(objects=self.report.stars[0]) 

184 # plot with first two objects as a list 

185 self.report.plotPerObjectAirMass(objects=self.report.stars[0:2]) 

186 # flip y axis option 

187 self.report.plotPerObjectAirMass(airmassOneAtTop=True) 

188 # flip and select stars 

189 self.report.plotPerObjectAirMass(objects=self.report.stars[0], airmassOneAtTop=True) # both 

190 

191 def test_makeAltAzCoveragePlot(self): 

192 """Test that a the polar coverage plotting code runs.""" 

193 # We assume matplotlib is making plots, so just check that these 

194 # don't crash. 

195 

196 # test the default case 

197 self.report.makeAltAzCoveragePlot() 

198 # plot with only one object as a str not a list of str 

199 self.report.makeAltAzCoveragePlot(objects=self.report.stars[0]) 

200 # plot with first two objects as a list 

201 self.report.makeAltAzCoveragePlot(objects=self.report.stars[0:2]) 

202 # test turning lines off 

203 self.report.makeAltAzCoveragePlot(objects=self.report.stars[0:2], withLines=False) 

204 

205 def test_calcShutterTimes(self): 

206 timings = self.report.calcShutterTimes() 

207 if not timings: 

208 return # if the day has no on-sky observations, this returns None 

209 efficiency = 100 * (timings["scienceTimeTotal"] / timings["nightLength"]) 

210 self.assertGreater(efficiency, 0) 

211 self.assertLessEqual(efficiency, 100) 

212 

213 def test_getDatesForSeqNums(self): 

214 dateTimeDict = self.report.getDatesForSeqNums() 

215 self.assertIsInstance(dateTimeDict, dict) 

216 self.assertTrue(all(isinstance(seqNum, int) for seqNum in dateTimeDict.keys())) 

217 self.assertTrue(all(isinstance(seqNum, datetime.datetime) for seqNum in dateTimeDict.values())) 

218 

219 def test_doesNotRaise(self): 

220 """Tests for things which are hard to test, so just make sure they 

221 run. 

222 """ 

223 self.report.printShutterTimes() 

224 for sample, includeRaw in itertools.product((True, False), (True, False)): 

225 self.report.printAvailableKeys(sample=sample, includeRaw=includeRaw) 

226 self.report.printObsTable() 

227 for threshold, includeCalibs in itertools.product((0, 1, 10), (True, False)): 

228 self.report.printObsGaps(threshold=threshold, includeCalibs=includeCalibs) 

229 

230 def test_internals(self): 

231 startNum = self.report.getObservingStartSeqNum() 

232 self.assertIsInstance(startNum, int) 

233 self.assertGreater(startNum, 0) # the day starts at 1, so zero would be an error of some sort 

234 

235 starsFromGetter = self.report.getObservedObjects() 

236 self.assertIsInstance(starsFromGetter, list) 

237 self.assertSetEqual(set(starsFromGetter), set(self.report.stars)) 

238 

239 starsFromGetter = self.report.getObservedObjects(ignoreTileNum=True) 

240 self.assertLessEqual(len(starsFromGetter), len(self.report.stars)) 

241 

242 # check the internal color map has the right number of items 

243 self.assertEqual(len(self.report.cMap), len(starsFromGetter)) 

244 

245 

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

247 pass 

248 

249 

250def setup_module(module): 

251 lsst.utils.tests.init() 

252 

253 

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

255 lsst.utils.tests.init() 

256 unittest.main()