Coverage for tests/test_tmaUtils.py: 24%
356 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-17 04:43 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-17 04:43 -0700
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 asyncio
25import os
26import unittest
28import matplotlib.pyplot as plt
29import numpy as np
30import pandas as pd
31from astropy.time import TimeDelta
32from utils import getVcr
34import lsst.utils.tests
35from lsst.summit.utils.efdUtils import calcNextDay, getDayObsStartTime, makeEfdClient
36from lsst.summit.utils.enums import PowerState
37from lsst.summit.utils.tmaUtils import (
38 AxisMotionState,
39 TMAEvent,
40 TMAEventMaker,
41 TMAState,
42 TMAStateMachine,
43 _initializeTma,
44 filterBadValues,
45 getAxisAndType,
46 getAzimuthElevationDataForEvent,
47 getCommandsDuringEvent,
48 getSlewsFromEventList,
49 getTracksFromEventList,
50 plotEvent,
51)
53__all__ = [
54 "writeNewTmaEventTestTruthValues",
55]
57TESTDIR = os.path.abspath(os.path.dirname(__file__))
58vcr = getVcr()
61def getTmaEventTestTruthValues():
62 """Get the current truth values for the TMA event test cases.
64 Returns
65 -------
66 seqNums : `np.array` of `int`
67 The sequence numbers of the events.
68 startRows : `np.array` of `int`
69 The _startRow numbers of the events.
70 endRows : `np.array` of `int`
71 The _endRow numbers of the events.
72 types : `np.array` of `str`
73 The event types, as a string, i.e. the ``TMAEvent.name`` of the event's
74 ``event.type``.
75 endReasons : `np.array` of `str`
76 The event end reasons, as a string, i.e. the ``TMAEvent.name`` of the
77 event's ``event.endReason``.
78 """
79 dataFilename = os.path.join(TESTDIR, "data", "tmaEventData.txt")
81 seqNums, startRows, endRows, types, endReasons = np.genfromtxt(
82 dataFilename, delimiter=",", dtype=None, names=True, encoding="utf-8", unpack=True
83 )
84 return seqNums, startRows, endRows, types, endReasons
87def writeNewTmaEventTestTruthValues():
88 """This function is used to write out the truth values for the test cases.
90 If the internal event creation logic changes, these values can change, and
91 will need to be updated. Run this function, and check the new values into
92 git.
94 Note: if you have cause to update values with this function, make sure to
95 update the version number on the TMAEvent class.
96 """
97 dayObs = 20230531 # obviously must match the day in the test class
99 eventMaker = TMAEventMaker()
100 events = eventMaker.getEvents(dayObs)
102 dataFilename = os.path.join(TESTDIR, "data", "tmaEventData.txt")
104 columnHeader = "seqNum,startRow,endRow,type,endReason"
105 with open(dataFilename, "w") as f:
106 f.write(columnHeader + "\n")
107 for event in events:
108 line = (
109 f"{event.seqNum},{event._startRow},{event._endRow},{event.type.name},"
110 f"{event.endReason.name}"
111 )
112 f.write(line + "\n")
115def makeValid(tma):
116 """Helper function to turn a TMA into a valid state."""
117 for name, value in tma._parts.items():
118 if value == tma._UNINITIALIZED_VALUE:
119 tma._parts[name] = 1
122def _turnOn(tma):
123 """Helper function to turn TMA axes on for testing.
125 Do not call directly in normal usage or code, as this just arbitrarily
126 sets values to turn the axes on.
128 Parameters
129 ----------
130 tma : `lsst.summit.utils.tmaUtils.TMAStateMachine`
131 The TMA state machine model to initialize.
132 """
133 tma._parts["azimuthSystemState"] = PowerState.ON
134 tma._parts["elevationSystemState"] = PowerState.ON
137class TmaUtilsTestCase(lsst.utils.tests.TestCase):
138 def test_tmaInit(self):
139 tma = TMAStateMachine()
140 self.assertFalse(tma._isValid)
142 # setting one axis should not make things valid
143 tma._parts["azimuthMotionState"] = 1
144 self.assertFalse(tma._isValid)
146 # setting all the other components should make things valid
147 tma._parts["azimuthInPosition"] = 1
148 tma._parts["azimuthSystemState"] = 1
149 tma._parts["elevationInPosition"] = 1
150 tma._parts["elevationMotionState"] = 1
151 tma._parts["elevationSystemState"] = 1
152 self.assertTrue(tma._isValid)
154 def test_tmaReferences(self):
155 """Check the linkage between the component lists and the _parts
156 dict.
157 """
158 tma = TMAStateMachine()
160 # setting one axis should not make things valid
161 self.assertEqual(tma._parts["azimuthMotionState"], tma._UNINITIALIZED_VALUE)
162 self.assertEqual(tma._parts["elevationMotionState"], tma._UNINITIALIZED_VALUE)
163 tma.motion[0] = AxisMotionState.TRACKING # set azimuth to 0
164 tma.motion[1] = AxisMotionState.TRACKING # set azimuth to 0
165 self.assertEqual(tma._parts["azimuthMotionState"], AxisMotionState.TRACKING)
166 self.assertEqual(tma._parts["elevationMotionState"], AxisMotionState.TRACKING)
168 def test_getAxisAndType(self):
169 # check both the long and short form names work
170 for s in ["azimuthMotionState", "lsst.sal.MTMount.logevent_azimuthMotionState"]:
171 self.assertEqual(getAxisAndType(s), ("azimuth", "MotionState"))
173 # check in position, and use elevation instead of azimuth to test that
174 for s in ["elevationInPosition", "lsst.sal.MTMount.logevent_elevationInPosition"]:
175 self.assertEqual(getAxisAndType(s), ("elevation", "InPosition"))
177 for s in ["azimuthSystemState", "lsst.sal.MTMount.logevent_azimuthSystemState"]:
178 self.assertEqual(getAxisAndType(s), ("azimuth", "SystemState"))
180 def test_initStateLogic(self):
181 tma = TMAStateMachine()
182 self.assertFalse(tma._isValid)
183 self.assertFalse(tma.isMoving)
184 self.assertFalse(tma.canMove)
185 self.assertFalse(tma.isTracking)
186 self.assertFalse(tma.isSlewing)
187 self.assertEqual(tma.state, TMAState.UNINITIALIZED)
189 _initializeTma(tma) # we're valid, but still aren't moving and can't
190 self.assertTrue(tma._isValid)
191 self.assertNotEqual(tma.state, TMAState.UNINITIALIZED)
192 self.assertTrue(tma.canMove)
193 self.assertTrue(tma.isNotMoving)
194 self.assertFalse(tma.isMoving)
195 self.assertFalse(tma.isTracking)
196 self.assertFalse(tma.isSlewing)
198 _turnOn(tma) # can now move, still valid, but not in motion
199 self.assertTrue(tma._isValid)
200 self.assertTrue(tma.canMove)
201 self.assertTrue(tma.isNotMoving)
202 self.assertFalse(tma.isMoving)
203 self.assertFalse(tma.isTracking)
204 self.assertFalse(tma.isSlewing)
206 # consider manipulating the axes by hand here and testing these?
207 # it's likely not worth it, given how much this exercised elsewhere,
208 # but these are the only functions not yet being directly tested
209 # tma._axesInFault()
210 # tma._axesOff()
211 # tma._axesOn()
212 # tma._axesInMotion()
213 # tma._axesTRACKING()
214 # tma._axesInPosition()
217@vcr.use_cassette()
218class TMAEventMakerTestCase(lsst.utils.tests.TestCase):
219 @classmethod
220 @vcr.use_cassette()
221 def setUpClass(cls):
222 try:
223 cls.client = makeEfdClient(testing=True)
224 except RuntimeError:
225 raise unittest.SkipTest("Could not instantiate an EFD client")
227 cls.dayObs = 20230531
228 cls.dayObsWithBlockInfo = 20230615
229 # get a sample expRecord here to test expRecordToTimespan
230 cls.tmaEventMaker = TMAEventMaker(cls.client)
231 cls.events = cls.tmaEventMaker.getEvents(cls.dayObs) # does the fetch
232 cls.sampleData = cls.tmaEventMaker._data[cls.dayObs] # pull the data from the object and test length
234 @vcr.use_cassette()
235 def tearDown(self):
236 loop = asyncio.get_event_loop()
237 loop.run_until_complete(self.client.influx_client.close())
239 @vcr.use_cassette()
240 def test_events(self):
241 data = self.sampleData
242 self.assertIsInstance(data, pd.DataFrame)
243 self.assertEqual(len(data), 993)
245 @vcr.use_cassette()
246 def test_rowDataForValues(self):
247 rowsFor = set(self.sampleData["rowFor"])
248 self.assertEqual(len(rowsFor), 6)
250 # hard coding these ensures that you can't extend the axes/model
251 # without being explicit about it here.
252 correct = {
253 "azimuthInPosition",
254 "azimuthMotionState",
255 "azimuthSystemState",
256 "elevationInPosition",
257 "elevationMotionState",
258 "elevationSystemState",
259 }
260 self.assertSetEqual(rowsFor, correct)
262 @vcr.use_cassette()
263 def test_monotonicTimeInDataframe(self):
264 # ensure that each row is later than the previous
265 times = self.sampleData["private_efdStamp"]
266 self.assertTrue(np.all(np.diff(times) > 0))
268 @vcr.use_cassette()
269 def test_monotonicTimeApplicationOfRows(self):
270 # ensure you can apply rows in the correct order
271 tma = TMAStateMachine()
272 row1 = self.sampleData.iloc[0]
273 row2 = self.sampleData.iloc[1]
275 # just running this check it is OK
276 tma.apply(row1)
277 tma.apply(row2)
279 # and that if you apply them in reverse order then things will raise
280 tma = TMAStateMachine()
281 with self.assertRaises(ValueError):
282 tma.apply(row2)
283 tma.apply(row1)
285 @vcr.use_cassette()
286 def test_fullDaySequence(self):
287 # make sure we can apply all the data from the day without falling
288 # through the logic sieve
289 for engineering in (True, False):
290 tma = TMAStateMachine(engineeringMode=engineering)
292 _initializeTma(tma)
294 for rowNum, row in self.sampleData.iterrows():
295 tma.apply(row)
297 @vcr.use_cassette()
298 def test_endToEnd(self):
299 eventMaker = self.tmaEventMaker
300 events = eventMaker.getEvents(self.dayObs)
301 self.assertIsInstance(events, list)
302 self.assertEqual(len(events), 200)
303 self.assertIsInstance(events[0], TMAEvent)
305 slews = [e for e in events if e.type == TMAState.SLEWING]
306 tracks = [e for e in events if e.type == TMAState.TRACKING]
307 self.assertEqual(len(slews), 157)
308 self.assertEqual(len(tracks), 43)
310 seqNums, startRows, endRows, types, endReasons = getTmaEventTestTruthValues()
311 for eventNum, event in enumerate(events):
312 self.assertEqual(event.seqNum, seqNums[eventNum])
313 self.assertEqual(event._startRow, startRows[eventNum])
314 self.assertEqual(event._endRow, endRows[eventNum])
315 self.assertEqual(event.type.name, types[eventNum])
316 self.assertEqual(event.endReason.name, endReasons[eventNum])
318 eventSet = set(slews) # check we can hash
319 eventSet.update(slews) # check it ignores duplicates
320 self.assertEqual(len(eventSet), len(slews))
322 @vcr.use_cassette()
323 def test_noDataBehaviour(self):
324 eventMaker = self.tmaEventMaker
325 noDataDayObs = 19500101 # do not use 19700101 - there is data for that day!
326 with self.assertLogs(level="WARNING") as cm:
327 correctMsg = f"No EFD data found for dayObs={noDataDayObs}"
328 events = eventMaker.getEvents(noDataDayObs)
329 self.assertIsInstance(events, list)
330 self.assertEqual(len(events), 0)
331 msg = cm.output[0]
332 self.assertIn(correctMsg, msg)
334 @vcr.use_cassette()
335 def test_helperFunctions(self):
336 eventMaker = self.tmaEventMaker
337 events = eventMaker.getEvents(self.dayObs)
339 slews = [e for e in events if e.type == TMAState.SLEWING]
340 tracks = [e for e in events if e.type == TMAState.TRACKING]
341 foundSlews = getSlewsFromEventList(events)
342 foundTracks = getTracksFromEventList(events)
343 self.assertEqual(slews, foundSlews)
344 self.assertEqual(tracks, foundTracks)
346 def test_filterBadValues(self):
347 # NB: if you add enough spurious values that the median is no longer
348 # the value around which your "good" values are oscillating the first
349 # two points will get replaced and this can be very confusing!
351 # test no bad values
352 # mean = median = 1.0
353 values = np.array([1.0, 0.96, 1.0, 1.04, 0.95, 1.0, 1.05, 1.0, 1.05, 1.0, 0.95])
354 mean = np.mean(values)
355 nReplaced = filterBadValues(values)
356 self.assertEqual(nReplaced, 0)
357 self.assertEqual(np.mean(values), mean)
359 # test with one bad values
360 values = np.array([1.0, 0.96, 1.0, 1.04, 2.95, 1.0, 1.05, 1.0, 1.05, 1.0, 0.95])
361 nReplaced = filterBadValues(values)
362 self.assertEqual(nReplaced, 1)
364 # test with two consecutive bad values
365 values = np.array([1.0, 0.96, 1.0, 1.04, 2.95, 3.0, 1.05, 1.0, 1.05, 1.0, 0.95])
366 nReplaced = filterBadValues(values)
367 self.assertEqual(nReplaced, 2)
369 # test with three consecutive bad values
370 values = np.array([1.0, 0.96, 1.0, 1.04, 2.95, 3.0, 4.05, 1.0, 1.05, 1.0, 0.95])
371 nReplaced = filterBadValues(values)
372 self.assertEqual(nReplaced, 3)
374 # test with three consecutive bad values and another at the end
375 values = np.array([1.0, 0.96, 1.0, 1.04, 2.95, 3.0, 4.05, 1.0, 1.05, 1.0, 3.95])
376 nReplaced = filterBadValues(values)
377 self.assertEqual(nReplaced, 4)
379 # test with more than three consecutive bad values
380 values = np.array([1.0, 0.96, 1.0, 1.04, 2.95, 3.0, 4.05, 5.0, 1.05, 1.0, 0.95])
381 nReplaced = filterBadValues(values)
382 self.assertEqual(nReplaced, 3)
383 self.assertIn(5.0, values) # check the last bad value is still there specifically
385 # test with more than three consecutive bad values and another bad
386 # value at the end
387 values = np.array([1.0, 0.96, 1.0, 1.04, 2.95, 3.0, 4.05, 5.0, 1.05, 1.0, 2.95])
388 nReplaced = filterBadValues(values)
389 self.assertEqual(nReplaced, 4)
391 # test with bad values in first two positions
392 values = np.array([2.0, 1.96, 1.0, 1.04, 0.95, 1.0, 1.05, 1.0, 1.05, 1.0, 0.95]) # median = 1.0
393 nReplaced = filterBadValues(values)
394 self.assertEqual(nReplaced, 2)
396 # test with bad values in first two positions and one in the middle
397 values = np.array([2.0, 1.96, 1.0, 1.04, 0.95, 5.0, 1.04, 1.0, 1.05, 1.0, 0.95])
398 nReplaced = filterBadValues(values)
399 self.assertEqual(nReplaced, 3)
401 # check that the last two good values are always used for correction,
402 # including when there are more than three consecutive bad values.
403 values = np.array([1.0, 0.96, 1.0, 1.02, 2.95, 3.0, 4.05, 5.0, 1.05, 1.0, 2.95])
404 expected = np.array([1.0, 0.96, 1.0, 1.02, 1.01, 1.01, 1.01, 5.0, 1.05, 1.0, 1.025])
405 nReplaced = filterBadValues(values)
406 residuals = np.abs(values - expected)
407 self.assertEqual(nReplaced, 4)
408 self.assertTrue(np.all(residuals < 1e-6))
410 # check with one good point after an overflowing run of bad to make
411 # sure the correction is always applied with good values, not the naive
412 # average of the last two even if they might be bad
413 values = np.array([1.0, 0.96, 1.0, 1.02, 2.95, 3.0, 4.05, 5.0, 1.05, 2.95, 1.0])
414 expected = np.array([1.0, 0.96, 1.0, 1.02, 1.01, 1.01, 1.01, 5.0, 1.05, 1.035, 1.0])
415 nReplaced = filterBadValues(values)
416 residuals = np.abs(values - expected)
417 self.assertEqual(nReplaced, 4)
418 self.assertTrue(np.all(residuals < 1e-6))
420 # check with non-default maxDelta
421 values = np.array([1.0, 0.96, 1.0, 1.02, 2.95, 3.0, 4.05, 5.0, 1.05, 1.0, 2.95])
422 nReplaced = filterBadValues(values, maxDelta=10)
423 self.assertEqual(nReplaced, 0)
425 values = np.array(
426 [
427 1.0,
428 1.0,
429 1.0,
430 1.1,
431 1.0,
432 1.0,
433 1.0,
434 1.0,
435 1.0,
436 1.0,
437 ]
438 )
439 nReplaced = filterBadValues(values, maxDelta=0.01)
440 self.assertEqual(nReplaced, 1)
442 @vcr.use_cassette()
443 def test_getEvent(self):
444 # test the singular event getter, and what happens if the event doesn't
445 # exist for the day
446 eventMaker = self.tmaEventMaker
447 events = eventMaker.getEvents(self.dayObs)
448 nEvents = len(events)
450 event = eventMaker.getEvent(self.dayObs, 0)
451 self.assertIsInstance(event, TMAEvent)
452 self.assertEqual(event, events[0])
453 event = eventMaker.getEvent(self.dayObs, 100)
454 self.assertIsInstance(event, TMAEvent)
455 self.assertEqual(event, events[100])
457 with self.assertLogs(level="WARNING") as cm:
458 correctMsg = f"Event {nEvents+1} not found for {self.dayObs}"
459 event = eventMaker.getEvent(self.dayObs, nEvents + 1)
460 msg = cm.output[0]
461 self.assertIn(correctMsg, msg)
463 @vcr.use_cassette()
464 def test_printing(self):
465 eventMaker = self.tmaEventMaker
466 events = eventMaker.getEvents(self.dayObs)
468 # test str(), repr(), and _ipython_display_() for an event
469 print(str(events[0]))
470 print(repr(events[0]))
471 print(events[0]._ipython_display_())
473 # spot-check both a slow and a track to print
474 slews = [e for e in events if e.type == TMAState.SLEWING]
475 tracks = [e for e in events if e.type == TMAState.TRACKING]
476 eventMaker.printEventDetails(slews[0])
477 eventMaker.printEventDetails(tracks[0])
478 eventMaker.printEventDetails(events[-1])
480 # check the full day trick works
481 eventMaker.printFullDayStateEvolution(self.dayObs)
483 tma = TMAStateMachine()
484 _initializeTma(tma) # the uninitialized state contains wrong types for printing
485 eventMaker.printTmaDetailedState(tma)
487 @vcr.use_cassette()
488 def test_getAxisData(self):
489 eventMaker = self.tmaEventMaker
490 events = eventMaker.getEvents(self.dayObs)
492 azData, elData = getAzimuthElevationDataForEvent(self.client, events[0])
493 self.assertIsInstance(azData, pd.DataFrame)
494 self.assertIsInstance(elData, pd.DataFrame)
496 paddedAzData, paddedElData = getAzimuthElevationDataForEvent(
497 self.client, events[0], prePadding=2, postPadding=1
498 )
499 self.assertGreater(len(paddedAzData), len(azData))
500 self.assertGreater(len(paddedElData), len(elData))
502 # just check this doesn't raise when called, and check we can pass the
503 # data in
504 plotEvent(self.client, events[0], azimuthData=azData, elevationData=elData)
506 @vcr.use_cassette()
507 def test_plottingAndCommands(self):
508 eventMaker = self.tmaEventMaker
509 events = eventMaker.getEvents(self.dayObs)
510 event = events[28] # this one has commands, and we'll check that later
512 # check we _can_ plot without a figure, and then stop doing that
513 plotEvent(self.client, event)
515 fig = plt.figure(figsize=(10, 8))
516 # just check this doesn't raise when called
517 plotEvent(self.client, event, fig=fig)
518 plt.close(fig)
520 commandsToPlot = ["raDecTarget", "moveToTarget", "startTracking", "stopTracking"]
521 commands = getCommandsDuringEvent(self.client, event, commandsToPlot, doLog=False)
522 self.assertTrue(not all([time is None for time in commands.values()])) # at least one command
524 plotEvent(self.client, event, fig=fig, commands=commands)
526 del fig
528 @vcr.use_cassette()
529 def test_findEvent(self):
530 eventMaker = self.tmaEventMaker
531 events = eventMaker.getEvents(self.dayObs)
532 event = events[28] # this one has a contiguous event before it
534 time = event.begin
535 found = eventMaker.findEvent(time)
536 self.assertEqual(found, event)
538 dt = TimeDelta(0.01, format="sec")
539 # must be just inside to get the same event back, because if a moment
540 # is shared it gives the one which starts with the moment (whilst
541 # logging info messages about it)
542 time = event.end - dt
543 found = eventMaker.findEvent(time)
544 self.assertEqual(found, event)
546 # now check that if we're a hair after, we don't get the same event
547 time = event.end + dt
548 found = eventMaker.findEvent(time)
549 self.assertNotEqual(found, event)
551 # Now check the cases which don't find an event at all. It would be
552 # nice to check the log messages here, but it seems too fragile to be
553 # worth it
554 dt = TimeDelta(1, format="sec")
555 tooEarlyOnDay = getDayObsStartTime(self.dayObs) + dt # 1 second after start of day
556 found = eventMaker.findEvent(tooEarlyOnDay)
557 self.assertIsNone(found)
559 # 1 second before end of day and this day does not end with an open
560 # event
561 tooLateOnDay = getDayObsStartTime(calcNextDay(self.dayObs)) - dt
562 found = eventMaker.findEvent(tooLateOnDay)
563 self.assertIsNone(found)
565 # going just inside the last event of the day should be fine
566 lastEvent = events[-1]
567 found = eventMaker.findEvent(lastEvent.end - dt)
568 self.assertEqual(found, lastEvent)
570 # going at the very end of the last event of the day should actually
571 # find nothing, because the last moment of an event isn't actually in
572 # the event itself, because of how contiguous events are defined to
573 # behave (being half-open intervals)
574 found = eventMaker.findEvent(lastEvent.end)
575 self.assertIsNone(found, lastEvent)
577 @vcr.use_cassette()
578 def test_eventAssociatedWith(self):
579 eventMaker = self.tmaEventMaker
580 events = eventMaker.getEvents(self.dayObsWithBlockInfo)
581 eventsWithBlockInfo = [e for e in events if e.blockInfos]
582 eventsWithoutBlockInfo = [e for e in events if not e.blockInfos]
583 self.assertEqual(len(events), 69)
584 self.assertEqual(len(eventsWithBlockInfo), 65)
585 self.assertEqual(len(eventsWithoutBlockInfo), 4)
587 self.assertIsNotNone(eventsWithoutBlockInfo[0].blockInfos)
588 self.assertIsInstance(eventsWithoutBlockInfo[0].blockInfos, list)
589 self.assertEqual(len(eventsWithoutBlockInfo[0].blockInfos), 0)
591 event = eventsWithBlockInfo[0]
592 self.assertIsInstance(event, TMAEvent)
593 self.assertTrue(event.associatedWith(ticket="SITCOM-906"))
594 self.assertFalse(event.associatedWith(ticket="SITCOM-905"))
596 self.assertTrue(event.associatedWith(salIndex=100017))
597 self.assertFalse(event.associatedWith(salIndex=100018))
599 self.assertTrue(event.associatedWith(block=6))
600 self.assertFalse(event.associatedWith(block=5))
602 # check it works with any and all of the arguments
603 self.assertTrue(event.associatedWith(block=6, salIndex=100017))
604 self.assertTrue(event.associatedWith(block=6, salIndex=100017, ticket="SITCOM-906"))
606 # check it's false if any are false
607 self.assertFalse(event.associatedWith(block=7, salIndex=100017, ticket="SITCOM-906")) # 1 wrong
608 self.assertFalse(event.associatedWith(block=6, salIndex=100018, ticket="SITCOM-906")) # 1 wrong
609 self.assertFalse(event.associatedWith(block=6, salIndex=100017, ticket="SITCOM-907")) # 1 wrong
610 self.assertFalse(event.associatedWith(block=1, salIndex=1, ticket="SITCOM-1")) # all wrong
612 # check with the blockSeqNum, with and without the other items
613 self.assertTrue(event.associatedWith(block=6, blockSeqNum=1))
614 self.assertFalse(event.associatedWith(block=6, blockSeqNum=2))
615 self.assertTrue(event.associatedWith(block=6, blockSeqNum=1, salIndex=100017))
616 self.assertFalse(event.associatedWith(block=6, blockSeqNum=2, salIndex=100017))
617 self.assertTrue(event.associatedWith(block=6, blockSeqNum=1, salIndex=100017, ticket="SITCOM-906"))
618 self.assertFalse(event.associatedWith(block=6, blockSeqNum=2, salIndex=100017, ticket="SITCOM-906"))
620 with self.assertRaises(ValueError):
621 event.associatedWith()
622 event.associatedWith(blockSeqNum=1) # nonsense to ask for a seqNum without a block number
625class TestMemory(lsst.utils.tests.MemoryTestCase):
626 pass
629def setup_module(module):
630 lsst.utils.tests.init()
633if __name__ == "__main__": 633 ↛ 634line 633 didn't jump to line 634, because the condition on line 633 was never true
634 lsst.utils.tests.init()
635 unittest.main()