Ejemplo n.º 1
0
 def test_string_sensor(self):
     """Test string sensor."""
     s = DeviceTestSensor(
         katcp.Sensor.STRING, "a.string", "A string sensor.", "filename",
         None,
         timestamp=12345, status=katcp.Sensor.NOMINAL, value="zwoop"
     )
     self.assertEqual(s.read_formatted(), ("12345000", "nominal", "zwoop"))
     self.assertEquals(s.parse_value("bar foo"), "bar foo")
Ejemplo n.º 2
0
 def test_discrete_sensor(self):
     """Test discrete sensor."""
     s = DeviceTestSensor(
             katcp.Sensor.DISCRETE, "a.discrete", "A discrete sensor.", "state",
             ["on", "off"],
             timestamp=12345, status=katcp.Sensor.ERROR, value="on"
     )
     self.assertEqual(s.read_formatted(), ("12345000", "error", "on"))
     self.assertEquals(s.parse_value("on"), "on")
     self.assertRaises(ValueError, s.parse_value, "fish")
Ejemplo n.º 3
0
 def test_timestamp_sensor(self):
     """Test timestamp sensor."""
     s = DeviceTestSensor(
         katcp.Sensor.TIMESTAMP, "a.timestamp", "A timestamp sensor.", "ms",
         None,
         timestamp=12345, status=katcp.Sensor.NOMINAL, value=1001.9
     )
     self.assertEqual(s.read_formatted(), ("12345000", "nominal", "1001900"))
     self.assertAlmostEqual(s.parse_value("1002100"), 1002.1)
     self.assertRaises(ValueError, s.parse_value, "bicycle")
Ejemplo n.º 4
0
    def setUp(self):
        """Set up for test."""
        # test sensor
        # self._print_lock = threading.Lock()
        self._time_lock = threading.Lock()
        self._next_wakeup = 1e99
        self.wake_waits = Queue.Queue()
        self.sensor = DeviceTestSensor(
                Sensor.INTEGER, "an.int", "An integer.", "count",
        [-4, 3],
                timestamp=12345, status=Sensor.NOMINAL, value=3)


        # test callback
        self.inform_called = threading.Event()
        def inform(sensor_name, timestamp, status, value):
            self.inform_called.set()
            self.calls.append((self.time(),
                               (sensor_name, float(timestamp), status, value)) )

        def next_wakeup_callback(timeout):
            with self._time_lock:
                if timeout is not None:
                    next_wake = self.time() + timeout
                    self._next_wakeup = min(self._next_wakeup, next_wake)
                else:
                    self._next_wakeup = 1e99
            self.wake_waits.put(timeout)

        def waited_callback(start, end, timeout):
            # A callback that updates 'simulated time' whenever wake.wait() is
            # called
            with self._time_lock:
                self._next_wakeup = 1e99

        # test reactor
        self.reactor = sampling.SampleReactor()

        # Patch time.time so that we can lie about time.
        self.time_patcher = mock.patch('katcp.sampling.time')
        mtime = self.time_patcher.start()
        self.addCleanup(self.time_patcher.stop)
        self.time = mtime.time
        self.start_time = self.time.return_value = 0

        # Replace the reactor wake Event with a time-warping mock Event
        self.reactor._wakeEvent = self.wake = wake = FakeEvent(
            self.time, next_wakeup_callback, waited_callback)

        start_thread_with_cleanup(self, self.reactor)
        # Wait for the event loop to reach its first wake.wait()
        self.wake_waits.get(timeout=1)

        self.calls = []
        self.inform = inform
Ejemplo n.º 5
0
 def test_lru_sensor(self):
     """Test LRU sensor."""
     s = DeviceTestSensor(
             katcp.Sensor.LRU, "an.lru", "An LRU sensor.", "state",
             None,
             timestamp=12345, status=katcp.Sensor.FAILURE,
             value=katcp.Sensor.LRU_ERROR
     )
     self.assertEqual(s.read_formatted(), ("12345000", "failure", "error"))
     self.assertEquals(s.parse_value("nominal"), katcp.Sensor.LRU_NOMINAL)
     self.assertRaises(ValueError, s.parse_value, "fish")
Ejemplo n.º 6
0
 def test_boolean_sensor(self):
     """Test boolean sensor."""
     s = DeviceTestSensor(
             katcp.Sensor.BOOLEAN, "a.boolean", "A boolean.", "on/off",
             None,
             timestamp=12345, status=katcp.Sensor.UNKNOWN, value=True
     )
     self.assertEqual(s.read_formatted(), ("12345000", "unknown", "1"))
     self.assertEquals(s.parse_value("1"), True)
     self.assertEquals(s.parse_value("0"), False)
     self.assertRaises(ValueError, s.parse_value, "asd")
Ejemplo n.º 7
0
    def setUp(self):
        """Set up for test."""
        # test sensor
        self.sensor = DeviceTestSensor(
                Sensor.INTEGER, "an.int", "An integer.", "count",
                [-4, 3],
                timestamp=12345, status=Sensor.NOMINAL, value=3)

        # test callback
        def inform(sensor_name, timestamp, status, value):
            self.calls.append(sampling.format_inform_v5(
                sensor_name, timestamp, status, value) )

        self.calls = []
        self.inform = inform
    def test_sensor_add_remove(self):
        """Test a sensor being added and then remove it."""
        yield self.client.until_synced()

        sensor = DeviceTestSensor(Sensor.INTEGER, "another.int",
                                  "An Integer.",
                                  "count", [-5, 5], timestamp=time.time(),
                                  status=Sensor.NOMINAL, value=3)
        # Check that the sensor does not exist currently
        self.assertNotIn(sensor.name, self.client.sensors)

        # Add a sensor.
        self.server.add_sensor(sensor)
        self.server.mass_inform(Message.inform('interface-changed'))
        # Do a blocking request to ensure #interface-changed has been received
        yield self.client.simple_request('watchdog')
        yield self.client.until_synced()
        self.assertIn('another.int', self.client.sensors)

        # Remove a sensor.
        self.server.remove_sensor(sensor)
        self.server.mass_inform(Message.inform('interface-changed'))
        # Do a blocking request to ensure #interface-changed has been received
        yield self.client.simple_request('watchdog')

        yield self.client.until_synced()
        self.assertNotIn('another.int', self.client.sensors)
Ejemplo n.º 9
0
    def setUp(self):
        """Set up for test."""
        # test sensor
        self.sensor = DeviceTestSensor(katcp.Sensor.INTEGER,
                                       "an.int",
                                       "An integer.",
                                       "count", [-4, 3],
                                       timestamp=12345,
                                       status=katcp.Sensor.NOMINAL,
                                       value=3)

        # test callback
        def inform(msg):
            self.calls.append(msg)

        self.calls = []
        self.inform = inform
Ejemplo n.º 10
0
    def setUp(self):
        """Set up for test."""
        # test sensor
        super(TestSampling, self).setUp()
        self.sensor = DeviceTestSensor(Sensor.INTEGER,
                                       "an.int",
                                       "An integer.",
                                       "count", [-40, 30],
                                       timestamp=self.ioloop_time,
                                       status=Sensor.NOMINAL,
                                       value=3)

        # test callback
        def inform(sensor, reading):
            assert get_ident() == self.ioloop_thread_id, (
                "inform must be called from in the ioloop")
            self.calls.append((sensor, reading))

        self.calls = []
        self.inform = inform
Ejemplo n.º 11
0
    def test_set_and_get_value(self):
        """Test getting and setting a sensor value."""
        s = DeviceTestSensor(
                katcp.Sensor.INTEGER, "an.int", "An integer.", "count",
                [-4, 3],
                timestamp=12345, status=katcp.Sensor.NOMINAL, value=3
        )

        self.assertEqual(s.value(), 3)

        s.set_value(2)
        self.assertEqual(s.value(), 2)

        s.set_value(3, timestamp=12345)
        self.assertEqual(s.read(), (12345, katcp.Sensor.NOMINAL, 3))

        self.assertRaises(ValueError, s.set_value, 5)
Ejemplo n.º 12
0
    def setUp(self):
        """Set up for test."""
        # test sensor
        super(TestSampling, self).setUp()
        self.sensor = DeviceTestSensor(
                Sensor.INTEGER, "an.int", "An integer.", "count",
                [-40, 30],
                timestamp=self.ioloop_time, status=Sensor.NOMINAL, value=3)
        # test callback
        def inform(sensor, reading):
            assert get_ident() == self.ioloop_thread_id, (
                "inform must be called from in the ioloop")
            self.calls.append((sensor, reading))

        self.calls = []
        self.inform = inform
Ejemplo n.º 13
0
    def test_float_sensor(self):
        """Test float sensor."""
        s = DeviceTestSensor(
                katcp.Sensor.FLOAT, "a.float", "A float.", "power",
                [0.0, 5.0],
                timestamp=12345, status=katcp.Sensor.WARN, value=3.0
        )
        self.assertEqual(s.read_formatted(), ("12345000", "warn", "3"))
        self.assertEquals(s.parse_value("3"), 3.0)
        self.assertRaises(ValueError, s.parse_value, "10")
        self.assertRaises(ValueError, s.parse_value, "-10")
        self.assertRaises(ValueError, s.parse_value, "asd")

        s = katcp.Sensor(katcp.Sensor.FLOAT, "a.float", "A float.", "", [-20.0, 20.0])
        self.assertEquals(s.value(), 0.0)
        s = katcp.Sensor(katcp.Sensor.FLOAT, "a.float", "A float.", "", [2.0, 20.0])
        self.assertEquals(s.value(), 2.0)
        s = katcp.Sensor(katcp.Sensor.FLOAT, "a.float", "A float.", "", [2.0, 20.0], default=5.0)
        self.assertEquals(s.value(), 5.0)
Ejemplo n.º 14
0
    def test_int_sensor(self):
        """Test integer sensor."""
        s = DeviceTestSensor(
                katcp.Sensor.INTEGER, "an.int", "An integer.", "count",
                [-4, 3],
                timestamp=12345, status=katcp.Sensor.NOMINAL, value=3
        )
        self.assertEqual(s.read_formatted(), ("12345000", "nominal", "3"))
        self.assertEquals(s.parse_value("3"), 3)
        self.assertRaises(ValueError, s.parse_value, "4")
        self.assertRaises(ValueError, s.parse_value, "-10")
        self.assertRaises(ValueError, s.parse_value, "asd")

        s = katcp.Sensor(katcp.Sensor.INTEGER, "an.int", "An integer.", "count", [-20, 20])
        self.assertEquals(s.value(), 0)
        s = katcp.Sensor(katcp.Sensor.INTEGER, "an.int", "An integer.", "count", [2, 20])
        self.assertEquals(s.value(), 2)
        s = katcp.Sensor(katcp.Sensor.INTEGER, "an.int", "An integer.", "count", [2, 20], default=5)
        self.assertEquals(s.value(), 5)
Ejemplo n.º 15
0
class TestSampling(TimewarpAsyncTestCase):
    # TODO Also test explicit ioloop passing

    def setUp(self):
        """Set up for test."""
        # test sensor
        super(TestSampling, self).setUp()
        self.sensor = DeviceTestSensor(
                Sensor.INTEGER, "an.int", "An integer.", "count",
                [-40, 30],
                timestamp=self.ioloop_time, status=Sensor.NOMINAL, value=3)
        # test callback
        def inform(sensor, reading):
            assert get_ident() == self.ioloop_thread_id, (
                "inform must be called from in the ioloop")
            self.calls.append((sensor, reading))

        self.calls = []
        self.inform = inform

    def test_sampling(self):
        """Test getting and setting the sampling."""
        s = self.sensor

        sampling.SampleNone(None, s)
        sampling.SampleAuto(None, s)
        sampling.SamplePeriod(None, s, 10)
        sampling.SampleEvent(None, s)
        sampling.SampleDifferential(None, s, 2)
        self.assertRaises(ValueError, sampling.SampleNone, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleAuto, None, s, "bar")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s)
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "0")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "-1")
        self.assertRaises(ValueError, sampling.SampleEvent, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s)
        self.assertRaises(ValueError, sampling.SampleDifferential,
                          None, s, "-1")
        self.assertRaises(ValueError, sampling.SampleDifferential,
                          None, s, "1.5")

        sampling.SampleStrategy.get_strategy("none", None, s)
        sampling.SampleStrategy.get_strategy("auto", None, s)
        sampling.SampleStrategy.get_strategy("period", None, s, "15")
        sampling.SampleStrategy.get_strategy("event", None, s)
        sampling.SampleStrategy.get_strategy("differential", None, s, "2")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "random", None, s)
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "period", None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "differential", None, s, "bar")

    @tornado.testing.gen_test(timeout=200)
    # Timeout needs to be longer than 'fake' duration of the test, since the tornado
    # ioloop is using out time-warped clock to determine timeouts too!
    def test_periodic(self):
        t0 = self.ioloop_time
        sample_p = 10                            # sample DUT in seconds
        DUT = sampling.SamplePeriod(self.inform, self.sensor, sample_p)
        self.assertEqual(self.calls, [])
        t, status, value = self.sensor.read()
        DUT.start()
        yield self.wake_ioloop()
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Warp the ioloop clock forward a bit more than one DUT. Check that
        #   1) a sample is sent,
        #   2) the next sample is scheduled at t0+DUT, not t0+DUT+extra delay
        yield self.set_ioloop_time(t0 + sample_p*1.15)
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Don't expect an update, since we are at just before the next sample DUT
        yield self.set_ioloop_time(t0 + sample_p*1.99)
        self.assertEqual(self.calls, [])
        # Now we are at exactly the next sample time, expect update
        yield self.set_ioloop_time(t0 + sample_p*2)
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Bit past previous sample time, expect no update
        yield self.set_ioloop_time(t0 + sample_p*2.16)
        self.assertEqual(self.calls, [])

        # Check that no update is sent if the sensor is updated, but that the next
        # periodic update is correct
        t, status, value = (t0 + sample_p*2.5, Sensor.WARN, -1)
        self.sensor.set(t, status, value)
        yield self.set_ioloop_time(t0 + sample_p*2.6)
        self.assertEqual(self.calls, [])
        yield self.set_ioloop_time(t0 + sample_p*3)
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Cancel strategy and check that its timeout call is cancelled.
        DUT.cancel()
        yield self.wake_ioloop()
        yield self.set_ioloop_time(t0 + sample_p*4.1)
        self.assertEqual(self.calls, [])


    @tornado.testing.gen_test(timeout=200)
    def test_auto(self):
        t0 = self.ioloop_time
        DUT = sampling.SampleAuto(self.inform, self.sensor)
        self.assertEqual(self.calls, [])
        t, status, value = self.sensor.read()
        DUT.start()
        yield self.wake_ioloop()
        # Check that it is attached
        self.assertTrue(DUT in self.sensor._observers)
        # The initial update
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Move along in time, don't expect any updates
        yield self.set_ioloop_time(t0 + 20)
        self.assertEqual(self.calls, [])
        # Now update the sensor a couple of times
        t1, status1, value1 = t0 + 21, Sensor.ERROR, 2
        t2, status2, value2 = t0 + 22, Sensor.NOMINAL, -1
        self.sensor.set(t1, status1, value1)
        self.sensor.set(t2, status2, value2)
        self.assertEqual(self.calls, [(self.sensor, (t1, status1, value1)),
                                      (self.sensor, (t2, status2, value2))])
        self.calls = []

        self._thread_update_check(t, status, value)
        yield self._check_cancel(DUT)

    @gen.coroutine
    def _thread_update_check(self, ts, status, value):
        # Check update from thread (inform() raises if called from the wrong thread)
        # Clears out self.calls before starting
        self.calls = []
        f = concurrent.futures.Future()
        def do_update():
            try:
                self.sensor.set(ts, status, value)
            finally:
                f.set_result(None)
            return f
        t = threading.Thread(target=do_update)
        t.start()
        yield f
        yield self.wake_ioloop()
        self.assertEqual(self.calls, [(self.sensor, (ts, status, value))])

    @gen.coroutine
    def _check_cancel(self, DUT):
        # Check post-cancel cleanup
        DUT.cancel()
        yield self.wake_ioloop()
        self.assertFalse(DUT in self.sensor._observers)

    @tornado.testing.gen_test(timeout=200)
    def test_differential(self):
        """Test SampleDifferential strategy."""
        t, status, value = self.sensor.read()
        delta = 3
        DUT = sampling.SampleDifferential(self.inform, self.sensor, delta)
        self.assertEqual(len(self.calls), 0)
        DUT.start()
        yield self.wake_ioloop()
        # Check initial update
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Some Updates less than delta from intial value
        self.sensor.set_value(value + 1)
        self.sensor.set_value(value + delta)
        self.sensor.set_value(value)
        self.sensor.set_value(value - 2)
        self.assertEqual(len(self.calls), 0)
        # Now an update bigger than delta from initial value
        self.sensor.set(t, status, value + delta + 1)
        yield self.wake_ioloop()
        self.assertEqual(self.calls, [(self.sensor, (t, status, value + delta + 1))])
        self.calls = []
        # Now change only the status, should update
        t, status, value = self.sensor.read()
        self.sensor.set(t, Sensor.ERROR, value)
        self.assertEqual(self.calls, [(self.sensor, (t, Sensor.ERROR, value))])
        # Test threaded update
        yield self._thread_update_check(t, status, value)
        yield self._check_cancel(DUT)

    def test_differential_timestamp(self):
        # Test that the timetamp differential is stored correctly as
        # seconds. This is mainly to check the conversion of the katcp spec from
        # milliseconds to seconds for katcp v5 spec.
        time_diff = 4.12                  # Time differential in seconds
        ts_sensor = Sensor(Sensor.TIMESTAMP, 'ts', 'ts sensor', '')
        diff = sampling.SampleDifferential(self.inform, ts_sensor, time_diff)
        self.assertEqual(diff._threshold, time_diff)

    @tornado.testing.gen_test(timeout=200)
    def test_event_rate(self):
        """Test SampleEventRate strategy."""
        shortest = 1.5
        longest = 4.5
        t, status, value = self.sensor.read()

        DUT = sampling.SampleEventRate(self.inform, self.sensor, shortest, longest)
        self.assertEqual(len(self.calls), 0)
        DUT.start()
        yield self.wake_ioloop()
        # Check initial update
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Too soon, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest*0.99)
        value = value + 3
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Too soon again, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest*0.999)
        value = value + 1
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Should now get minimum time update
        yield self.set_ioloop_time(t_last_sent + shortest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest*0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before next longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest*0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Set identical value, jump past min update time, no update should happen
        self.sensor.set(self.ioloop_time, status, value)
        yield self.set_ioloop_time(t_last_sent + shortest)
        self.assertEqual(len(self.calls), 0)

        # Set new value, update should happen
        value = value - 2
        self.sensor.set(self.ioloop_time, status, value)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t_last_sent, status, value))])
        self.calls = []

        # Now warp to after min period, change only status, update should happen
        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.ERROR
        self.sensor.set(self.ioloop_time, status, value)
        self.assertEqual(self.calls, [(self.sensor, (self.ioloop_time, status, value))])
        t_last_sent = self.ioloop_time
        self.calls = []

        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.NOMINAL
        value = value + 1
        yield self._thread_update_check(self.ioloop_time, status, value)
        yield self._check_cancel(DUT)
        self.calls = []

        # Since strategy is cancelled, no futher updates should be sent
        yield self.set_ioloop_time(self.ioloop_time + 5*longest)
        self.sensor.set(self.ioloop_time, Sensor.WARN, value + 3)
        self.assertEqual(len(self.calls), 0)

    @tornado.testing.gen_test(timeout=2000000)
    def test_event(self):
        """Test SampleEvent strategy."""
        DUT = sampling.SampleEvent(self.inform, self.sensor)
        self.assertEqual(DUT.get_sampling_formatted(), ('event', []) )
        self.assertEqual(self.calls, [])
        DUT.start()
        yield self.wake_ioloop()
        # Check intial update
        self.assertEqual(len(self.calls), 1)

        # Jump forward a lot, should not result in another sample
        yield self.set_ioloop_time(200000)
        self.assertEqual(len(self.calls), 1)

        self.sensor.set_value(2, status=Sensor.NOMINAL)
        self.assertEqual(len(self.calls), 2)

        # Test that an update is suppressed if the sensor value is unchanged
        self.sensor.set_value(2, status=Sensor.NOMINAL)
        self.assertEqual(len(self.calls), 2)

        # Test that an update happens if the status changes even if the value is
        # unchanged
        self.sensor.set_value(2, status=Sensor.WARN)
        self.assertEqual(len(self.calls), 3)

    @tornado.testing.gen_test(timeout=200)
    def test_differential_rate(self):
        shortest = 1.5
        longest = 4.5
        delta = 2
        t, status, value = self.sensor.read()
        # TODO change to be differentialrate test

        DUT = sampling.SampleDifferentialRate(
            self.inform, self.sensor, delta, shortest, longest)
        self.assertEqual(len(self.calls), 0)
        DUT.start()
        yield self.wake_ioloop()
        # Check initial update
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Too soon, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest*0.99)
        value = value + delta + 1
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Too soon again, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest*0.999)
        value = value + delta + 1
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Should now get minimum time update
        yield self.set_ioloop_time(t_last_sent + shortest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest*0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before next longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest*0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Set value with to small a change, jump past min update time, no update should
        # happen
        value = value - delta
        self.sensor.set(self.ioloop_time, status, value)
        yield self.set_ioloop_time(t_last_sent + shortest)
        self.assertEqual(len(self.calls), 0)

        # Set new value with large enough difference, update should happen
        value = value - 1
        self.sensor.set(self.ioloop_time, status, value)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t_last_sent, status, value))])
        self.calls = []

        # Now warp to after min period, change only status, update should happen
        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.ERROR
        self.sensor.set(self.ioloop_time, status, value)
        self.assertEqual(self.calls, [(self.sensor, (self.ioloop_time, status, value))])
        t_last_sent = self.ioloop_time
        self.calls = []

        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.NOMINAL
        value = value + 1
        yield self._thread_update_check(self.ioloop_time, status, value)
        yield self._check_cancel(DUT)
        self.calls = []

        # Since strategy is cancelled, no futher updates should be sent
        yield self.set_ioloop_time(self.ioloop_time + 5*longest)
        self.sensor.set(self.ioloop_time, Sensor.WARN, value + 3)
        self.assertEqual(len(self.calls), 0)
Ejemplo n.º 16
0
class TestSampling(TimewarpAsyncTestCase):
    # TODO Also test explicit ioloop passing

    def setUp(self):
        """Set up for test."""
        # test sensor
        super(TestSampling, self).setUp()
        self.sensor = DeviceTestSensor(Sensor.INTEGER,
                                       "an.int",
                                       "An integer.",
                                       "count", [-40, 30],
                                       timestamp=self.ioloop_time,
                                       status=Sensor.NOMINAL,
                                       value=3)

        # test callback
        def inform(sensor, reading):
            assert get_ident() == self.ioloop_thread_id, (
                "inform must be called from in the ioloop")
            self.calls.append((sensor, reading))

        self.calls = []
        self.inform = inform

    def test_sampling(self):
        """Test getting and setting the sampling."""
        s = self.sensor

        sampling.SampleNone(None, s)
        sampling.SampleAuto(None, s)
        sampling.SamplePeriod(None, s, 10)
        sampling.SampleEvent(None, s)
        sampling.SampleDifferential(None, s, 2)
        self.assertRaises(ValueError, sampling.SampleNone, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleAuto, None, s, "bar")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s)
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "0")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "-1")
        self.assertRaises(ValueError, sampling.SampleEvent, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s)
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s,
                          "-1")
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s,
                          "1.5")

        sampling.SampleStrategy.get_strategy("none", None, s)
        sampling.SampleStrategy.get_strategy("auto", None, s)
        sampling.SampleStrategy.get_strategy("period", None, s, "15")
        sampling.SampleStrategy.get_strategy("event", None, s)
        sampling.SampleStrategy.get_strategy("differential", None, s, "2")
        sampling.SampleStrategy.get_strategy(b"none", None, s)
        sampling.SampleStrategy.get_strategy(b"auto", None, s)
        sampling.SampleStrategy.get_strategy(b"period", None, s, "15")
        sampling.SampleStrategy.get_strategy(b"event", None, s)
        sampling.SampleStrategy.get_strategy(b"differential", None, s, "2")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "random", None, s)
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "period", None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "differential", None, s, "bar")

    @tornado.testing.gen_test(timeout=200)
    # Timeout needs to be longer than 'fake' duration of the test, since the tornado
    # ioloop is using out time-warped clock to determine timeouts too!
    def test_periodic(self):
        t0 = self.ioloop_time
        sample_p = 10  # sample DUT in seconds
        DUT = sampling.SamplePeriod(self.inform, self.sensor, sample_p)
        self.assertEqual(DUT.get_sampling_formatted(), (b'period', [b'10']))
        self.assertEqual(self.calls, [])
        t, status, value = self.sensor.read()
        DUT.start()
        yield self.wake_ioloop()
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Warp the ioloop clock forward a bit more than one DUT. Check that
        #   1) a sample is sent,
        #   2) the next sample is scheduled at t0+DUT, not t0+DUT+extra delay
        yield self.set_ioloop_time(t0 + sample_p * 1.15)
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Don't expect an update, since we are at just before the next sample DUT
        yield self.set_ioloop_time(t0 + sample_p * 1.99)
        self.assertEqual(self.calls, [])
        # Now we are at exactly the next sample time, expect update
        yield self.set_ioloop_time(t0 + sample_p * 2)
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Bit past previous sample time, expect no update
        yield self.set_ioloop_time(t0 + sample_p * 2.16)
        self.assertEqual(self.calls, [])

        # Check that no update is sent if the sensor is updated, but that the next
        # periodic update is correct
        t, status, value = (t0 + sample_p * 2.5, Sensor.WARN, -1)
        self.sensor.set(t, status, value)
        yield self.set_ioloop_time(t0 + sample_p * 2.6)
        self.assertEqual(self.calls, [])
        yield self.set_ioloop_time(t0 + sample_p * 3)
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Cancel strategy and check that its timeout call is cancelled.
        DUT.cancel()
        yield self.wake_ioloop()
        yield self.set_ioloop_time(t0 + sample_p * 4.1)
        self.assertEqual(self.calls, [])

    @tornado.testing.gen_test(timeout=200)
    def test_auto(self):
        t0 = self.ioloop_time
        DUT = sampling.SampleAuto(self.inform, self.sensor)
        self.assertEqual(DUT.get_sampling_formatted(), (b'auto', []))
        self.assertEqual(self.calls, [])
        t, status, value = self.sensor.read()
        DUT.start()
        yield self.wake_ioloop()
        # Check that it is attached
        self.assertTrue(DUT in self.sensor._observers)
        # The initial update
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Move along in time, don't expect any updates
        yield self.set_ioloop_time(t0 + 20)
        self.assertEqual(self.calls, [])
        # Now update the sensor a couple of times
        t1, status1, value1 = t0 + 21, Sensor.ERROR, 2
        t2, status2, value2 = t0 + 22, Sensor.NOMINAL, -1
        self.sensor.set(t1, status1, value1)
        self.sensor.set(t2, status2, value2)
        self.assertEqual(self.calls, [(self.sensor, (t1, status1, value1)),
                                      (self.sensor, (t2, status2, value2))])
        self.calls = []

        self._thread_update_check(t, status, value)
        yield self._check_cancel(DUT)

    @gen.coroutine
    def _thread_update_check(self, ts, status, value):
        # Check update from thread (inform() raises if called from the wrong thread)
        # Clears out self.calls before starting
        self.calls = []
        f = concurrent.futures.Future()

        def do_update():
            try:
                self.sensor.set(ts, status, value)
            finally:
                f.set_result(None)
            return f

        t = threading.Thread(target=do_update)
        t.start()
        yield f
        yield self.wake_ioloop()
        self.assertEqual(self.calls, [(self.sensor, (ts, status, value))])

    @gen.coroutine
    def _check_cancel(self, DUT):
        # Check post-cancel cleanup
        DUT.cancel()
        yield self.wake_ioloop()
        self.assertFalse(DUT in self.sensor._observers)

    @tornado.testing.gen_test(timeout=200)
    def test_differential(self):
        """Test SampleDifferential strategy."""
        t, status, value = self.sensor.read()
        delta = 3
        DUT = sampling.SampleDifferential(self.inform, self.sensor, delta)
        self.assertEqual(DUT.get_sampling_formatted(),
                         (b'differential', [b'3']))
        self.assertEqual(len(self.calls), 0)
        DUT.start()
        yield self.wake_ioloop()
        # Check initial update
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []
        # Some Updates less than delta from initial value
        self.sensor.set_value(value + 1)
        self.sensor.set_value(value + delta)
        self.sensor.set_value(value)
        self.sensor.set_value(value - 2)
        self.assertEqual(len(self.calls), 0)
        # Now an update bigger than delta from initial value
        self.sensor.set(t, status, value + delta + 1)
        yield self.wake_ioloop()
        self.assertEqual(self.calls,
                         [(self.sensor, (t, status, value + delta + 1))])
        self.calls = []
        # Now change only the status, should update
        t, status, value = self.sensor.read()
        self.sensor.set(t, Sensor.ERROR, value)
        self.assertEqual(self.calls, [(self.sensor, (t, Sensor.ERROR, value))])
        # Test threaded update
        yield self._thread_update_check(t, status, value)
        yield self._check_cancel(DUT)

    def test_differential_timestamp(self):
        # Test that the timetamp differential is stored correctly as
        # seconds. This is mainly to check the conversion of the katcp spec from
        # milliseconds to seconds for katcp v5 spec.
        time_diff = 4.12  # Time differential in seconds
        ts_sensor = Sensor(Sensor.TIMESTAMP, 'ts', 'ts sensor', '')
        diff = sampling.SampleDifferential(self.inform, ts_sensor, time_diff)
        self.assertEqual(diff.get_sampling_formatted(),
                         (b'differential', [b'4.12']))
        self.assertEqual(diff._threshold, time_diff)

    @tornado.testing.gen_test(timeout=200)
    def test_event_rate(self):
        """Test SampleEventRate strategy."""
        shortest = 1.5
        longest = 4.5
        t, status, value = self.sensor.read()

        DUT = sampling.SampleEventRate(self.inform, self.sensor, shortest,
                                       longest)
        self.assertEqual(DUT.get_sampling_formatted(),
                         (b'event-rate', [b'1.5', b'4.5']))
        self.assertEqual(len(self.calls), 0)
        DUT.start()
        yield self.wake_ioloop()
        # Check initial update
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Too soon, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest * 0.99)
        value = value + 3
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Too soon again, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest * 0.999)
        value = value + 1
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Should now get minimum time update
        yield self.set_ioloop_time(t_last_sent + shortest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest * 0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before next longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest * 0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Set identical value, jump past min update time, no update should happen
        self.sensor.set(self.ioloop_time, status, value)
        yield self.set_ioloop_time(t_last_sent + shortest)
        self.assertEqual(len(self.calls), 0)

        # Set new value, update should happen
        value = value - 2
        self.sensor.set(self.ioloop_time, status, value)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls,
                         [(self.sensor, (t_last_sent, status, value))])
        self.calls = []

        # Now warp to after min period, change only status, update should happen
        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.ERROR
        self.sensor.set(self.ioloop_time, status, value)
        self.assertEqual(self.calls,
                         [(self.sensor, (self.ioloop_time, status, value))])
        t_last_sent = self.ioloop_time
        self.calls = []

        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.NOMINAL
        value = value + 1
        yield self._thread_update_check(self.ioloop_time, status, value)
        yield self._check_cancel(DUT)
        self.calls = []

        # Since strategy is cancelled, no futher updates should be sent
        yield self.set_ioloop_time(self.ioloop_time + 5 * longest)
        self.sensor.set(self.ioloop_time, Sensor.WARN, value + 3)
        self.assertEqual(len(self.calls), 0)

    @tornado.testing.gen_test(timeout=2000000)
    def test_event(self):
        """Test SampleEvent strategy."""
        DUT = sampling.SampleEvent(self.inform, self.sensor)
        self.assertEqual(DUT.get_sampling_formatted(), (b'event', []))
        self.assertEqual(self.calls, [])
        DUT.start()
        yield self.wake_ioloop()
        # Check initial update
        self.assertEqual(len(self.calls), 1)

        # Jump forward a lot, should not result in another sample
        yield self.set_ioloop_time(200000)
        self.assertEqual(len(self.calls), 1)

        self.sensor.set_value(2, status=Sensor.NOMINAL)
        self.assertEqual(len(self.calls), 2)

        # Test that an update is suppressed if the sensor value is unchanged
        self.sensor.set_value(2, status=Sensor.NOMINAL)
        self.assertEqual(len(self.calls), 2)

        # Test that an update happens if the status changes even if the value is
        # unchanged
        self.sensor.set_value(2, status=Sensor.WARN)
        self.assertEqual(len(self.calls), 3)

    @tornado.testing.gen_test(timeout=200)
    def test_differential_rate(self):
        delta = 2
        shortest = 1.5
        longest = 4.5
        t, status, value = self.sensor.read()

        DUT = sampling.SampleDifferentialRate(self.inform, self.sensor, delta,
                                              shortest, longest)
        self.assertEqual(DUT.get_sampling_formatted(),
                         (b'differential-rate', [b'2', b'1.5', b'4.5']))
        self.assertEqual(len(self.calls), 0)
        DUT.start()
        yield self.wake_ioloop()
        # Check initial update
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Too soon, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest * 0.99)
        value = value + delta + 1
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Too soon again, should not send update
        yield self.set_ioloop_time(t_last_sent + shortest * 0.999)
        value = value + delta + 1
        t = self.ioloop_time
        self.sensor.set(t, status, value)
        self.assertEqual(len(self.calls), 0)

        # Should now get minimum time update
        yield self.set_ioloop_time(t_last_sent + shortest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest * 0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Warp to just before next longest period, should not update
        yield self.set_ioloop_time(t_last_sent + longest * 0.999)
        self.assertEqual(len(self.calls), 0)

        # Warp to longest period, should update
        yield self.set_ioloop_time(t_last_sent + longest)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls, [(self.sensor, (t, status, value))])
        self.calls = []

        # Set value with to small a change, jump past min update time, no update should
        # happen
        value = value - delta
        self.sensor.set(self.ioloop_time, status, value)
        yield self.set_ioloop_time(t_last_sent + shortest)
        self.assertEqual(len(self.calls), 0)

        # Set new value with large enough difference, update should happen
        value = value - 1
        self.sensor.set(self.ioloop_time, status, value)
        t_last_sent = self.ioloop_time
        self.assertEqual(self.calls,
                         [(self.sensor, (t_last_sent, status, value))])
        self.calls = []

        # Now warp to after min period, change only status, update should happen
        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.ERROR
        self.sensor.set(self.ioloop_time, status, value)
        self.assertEqual(self.calls,
                         [(self.sensor, (self.ioloop_time, status, value))])
        t_last_sent = self.ioloop_time
        self.calls = []

        yield self.set_ioloop_time(t_last_sent + shortest)
        status = Sensor.NOMINAL
        value = value + 1
        yield self._thread_update_check(self.ioloop_time, status, value)
        yield self._check_cancel(DUT)
        self.calls = []

        # Since strategy is cancelled, no futher updates should be sent
        yield self.set_ioloop_time(self.ioloop_time + 5 * longest)
        self.sensor.set(self.ioloop_time, Sensor.WARN, value + 3)
        self.assertEqual(len(self.calls), 0)
Ejemplo n.º 17
0
class TestSampling(unittest.TestCase):

    def setUp(self):
        """Set up for test."""
        # test sensor
        self.sensor = DeviceTestSensor(
                Sensor.INTEGER, "an.int", "An integer.", "count",
                [-4, 3],
                timestamp=12345, status=Sensor.NOMINAL, value=3)

        # test callback
        def inform(sensor_name, timestamp, status, value):
            self.calls.append(sampling.format_inform_v5(
                sensor_name, timestamp, status, value) )

        self.calls = []
        self.inform = inform

    def test_sampling(self):
        """Test getting and setting the sampling."""
        s = self.sensor

        sampling.SampleNone(None, s)
        sampling.SampleAuto(None, s)
        sampling.SamplePeriod(None, s, 10)
        sampling.SampleEvent(None, s)
        sampling.SampleDifferential(None, s, 2)
        self.assertRaises(ValueError, sampling.SampleNone, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleAuto, None, s, "bar")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s)
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "0")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "-1")
        self.assertRaises(ValueError, sampling.SampleEvent, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s)
        self.assertRaises(ValueError, sampling.SampleDifferential,
                          None, s, "-1")
        self.assertRaises(ValueError, sampling.SampleDifferential,
                          None, s, "1.5")

        sampling.SampleStrategy.get_strategy("none", None, s)
        sampling.SampleStrategy.get_strategy("auto", None, s)
        sampling.SampleStrategy.get_strategy("period", None, s, "15")
        sampling.SampleStrategy.get_strategy("event", None, s)
        sampling.SampleStrategy.get_strategy("differential", None, s, "2")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "random", None, s)
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "period", None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "differential", None, s, "bar")

    def test_event(self):
        """Test SampleEvent strategy."""
        event = sampling.SampleEvent(self.inform, self.sensor)
        self.assertEqual(event.get_sampling_formatted(),
                         ('event', []) )

        self.assertEqual(self.calls, [])

        event.attach()
        self.assertEqual(len(self.calls), 1)

        self.sensor.set_value(2, status=Sensor.NOMINAL)
        self.assertEqual(len(self.calls), 2)

        # Test that an update is suppressed if the sensor value is unchanged
        self.sensor.set_value(2, status=Sensor.NOMINAL)
        self.assertEqual(len(self.calls), 2)

        # Test that an update happens if the status changes even if the value is
        # unchanged
        self.sensor.set_value(2, status=Sensor.WARN)
        self.assertEqual(len(self.calls), 3)


    def test_differential(self):
        """Test SampleDifferential strategy."""
        diff = sampling.SampleDifferential(self.inform, self.sensor, 5)
        self.assertEqual(self.calls, [])

        diff.attach()
        self.assertEqual(len(self.calls), 1)

    def test_differential_timestamp(self):
        # Test that the timetamp differential is stored correctly as
        # seconds. This is mainly to check the conversion of the katcp spec from
        # milliseconds to seconds for katcp v5 spec.
        time_diff = 4.12                  # Time differential in seconds
        ts_sensor = Sensor(Sensor.TIMESTAMP, 'ts', 'ts sensor', '')
        diff = sampling.SampleDifferential(self.inform, ts_sensor, time_diff)
        self.assertEqual(diff._threshold, time_diff)

    def test_periodic(self):
        """Test SamplePeriod strategy."""
        sample_p = 10                            # sample period in seconds
        period = sampling.SamplePeriod(self.inform, self.sensor, sample_p)
        self.assertEqual(self.calls, [])

        period.attach()
        self.assertEqual(self.calls, [])

        next_p = period.periodic(1)
        self.assertEqual(next_p, 1 + sample_p)
        self.assertEqual(len(self.calls), 1)

        next_p = period.periodic(11)
        self.assertEqual(len(self.calls), 2)
        self.assertEqual(next_p, 11 + sample_p)

        next_p = period.periodic(12)
        self.assertEqual(next_p, 12 + sample_p)
        self.assertEqual(len(self.calls), 3)

    def test_event_rate(self):
        """Test SampleEventRate strategy."""
        shortest = 10
        longest = 20
        patcher = mock.patch('katcp.sampling.time')
        mtime = patcher.start()
        self.addCleanup(patcher.stop)
        time_ = mtime.time
        time_.return_value = 1
        evrate = sampling.SampleEventRate(
            self.inform, self.sensor, shortest, longest)
        new_period = mock.Mock()
        evrate.set_new_period_callback(new_period)

        time_.return_value = 1

        self.assertEqual(self.calls, [])

        evrate.attach()
        # Check initial update
        self.assertEqual(len(self.calls), 1)

        # Too soon, should not send update
        self.sensor.set_value(1)
        self.assertEqual(len(self.calls), 1)
        # but should have requested a future update at now + shortest-time
        new_period.assert_called_once_with(evrate, 11)
        new_period.reset_mock()

        time_.return_value = 11
        next_p = evrate.periodic(11)
        self.assertEqual(len(self.calls), 2)
        self.assertEqual(new_period.called, False)

        evrate.periodic(12)
        self.assertEqual(len(self.calls), 2)
        self.assertEqual(new_period.called, False)
        evrate.periodic(13)
        self.assertEqual(len(self.calls), 2)
        self.assertEqual(new_period.called, False)

        evrate.periodic(31)
        self.assertEqual(len(self.calls), 3)

        time_.return_value = 32
        self.assertEqual(len(self.calls), 3)

        time_.return_value = 41
        self.sensor.set_value(2)
        self.assertEqual(len(self.calls), 4)

    def test_differential_rate(self):
        difference = 2
        shortest = 10
        longest = 20
        patcher = mock.patch('katcp.sampling.time')
        mtime = patcher.start()
        self.addCleanup(patcher.stop)
        time_ = mtime.time
        time_.return_value = 0
        drate = sampling.SampleDifferentialRate(
            self.inform, self.sensor, difference, shortest, longest)
        self.assertEqual(
            drate.get_sampling(), sampling.SampleStrategy.DIFFERENTIAL_RATE)

        new_period = mock.Mock()
        drate.set_new_period_callback(new_period)

        self.assertEqual(len(self.calls), 0)
        drate.attach()
        self.assertEqual(len(self.calls), 1)
        # Bigger than `difference`, but too soon
        self.sensor.set_value(0)
        # Should not have added a call
        self.assertEqual(len(self.calls), 1)
        # Should have requested a future update at shortest-time
        new_period.assert_called_once_with(drate, 10)
        new_period.reset_mock()

        # call before shortest update period
        drate.periodic(7)
        # Should not have added a call
        self.assertEqual(len(self.calls), 1)
        # Call at next period, should call, and schedule a periodic update
        # `longest` later
        next_p = drate.periodic(10)
        self.assertEqual(len(self.calls), 2)
        self.assertEqual(next_p, 30)
        next_p = drate.periodic(30)
        self.assertEqual(len(self.calls), 3)
        self.assertEqual(next_p, 50)

        # Update with a too-small difference in value, should not send an
        # update, nor should it schedule a shortest-time future-update
        self.sensor.set_value(-1)
        self.assertEqual(len(self.calls), 3)
        self.assertEqual(new_period.called, False)

        next_p = drate.periodic(50)
        self.assertEqual(len(self.calls), 4)
        self.assertEqual(next_p, 70)


    def test_event_rate_fractions(self):
        # Test SampleEventRate strategy in the presence of fractional seconds --
        # mainly to catch bugs when it was converted to taking seconds instead of
        # milliseconds, since the previous implementation used an integer number
        # of milliseconds
        shortest = 3./8
        longest = 6./8
        evrate = sampling.SampleEventRate(self.inform, self.sensor, shortest,
                                          longest)
        evrate.set_new_period_callback(mock.Mock())

        now = [0]
        evrate._time = lambda: now[0]

        evrate.attach()
        self.assertEqual(len(self.calls), 1)

        now[0] = 0.999*shortest
        self.sensor.set_value(1)
        self.assertEqual(len(self.calls), 1)

        now[0] = shortest
        self.sensor.set_value(1)
        self.assertEqual(len(self.calls), 2)

        next_time = evrate.periodic(now[0] + 0.99*shortest)
        self.assertEqual(len(self.calls), 2)
        self.assertEqual(next_time, now[0] + longest)
Ejemplo n.º 18
0
class TestReactorIntegration(unittest.TestCase):
    def setUp(self):
        """Set up for test."""
        # test sensor
        # self._print_lock = threading.Lock()
        self._time_lock = threading.Lock()
        self._next_wakeup = 1e99
        self.wake_waits = Queue.Queue()
        self.sensor = DeviceTestSensor(
                Sensor.INTEGER, "an.int", "An integer.", "count",
        [-4, 3],
                timestamp=12345, status=Sensor.NOMINAL, value=3)


        # test callback
        self.inform_called = threading.Event()
        def inform(sensor_name, timestamp, status, value):
            self.inform_called.set()
            self.calls.append((self.time(),
                               (sensor_name, float(timestamp), status, value)) )

        def next_wakeup_callback(timeout):
            with self._time_lock:
                if timeout is not None:
                    next_wake = self.time() + timeout
                    self._next_wakeup = min(self._next_wakeup, next_wake)
                else:
                    self._next_wakeup = 1e99
            self.wake_waits.put(timeout)

        def waited_callback(start, end, timeout):
            # A callback that updates 'simulated time' whenever wake.wait() is
            # called
            with self._time_lock:
                self._next_wakeup = 1e99

        # test reactor
        self.reactor = sampling.SampleReactor()

        # Patch time.time so that we can lie about time.
        self.time_patcher = mock.patch('katcp.sampling.time')
        mtime = self.time_patcher.start()
        self.addCleanup(self.time_patcher.stop)
        self.time = mtime.time
        self.start_time = self.time.return_value = 0

        # Replace the reactor wake Event with a time-warping mock Event
        self.reactor._wakeEvent = self.wake = wake = FakeEvent(
            self.time, next_wakeup_callback, waited_callback)

        start_thread_with_cleanup(self, self.reactor)
        # Wait for the event loop to reach its first wake.wait()
        self.wake_waits.get(timeout=1)

        self.calls = []
        self.inform = inform

    def _add_strategy(self, strat, wait_initial=True):
        # Add strategy to test reactor while taking care to mock time.time as
        # needed, and waits for the initial update (all strategies except None
        # should send an initial update)

        self.reactor.add_strategy(strat)

        if wait_initial:
            self.inform_called.wait(1)
            self.inform_called.clear()
            self.wake_waits.get(timeout=1)

    def timewarp(self, jump, wait_for_waitstate=True, event_to_await=None):
        """
        Timewarp simulation time by `jump` seconds

        Arguments
        ---------

        jump: float
            Number of seconds to time-warp by
        wait_for_waitstate: bool, default True
            Wait until the simulated loop again enters a wait state that times
            out beyond the current time-warp end-time. Will wake up the simulated
            loop as many times as necessary.
        event_to_await: Event or None, default None
            If an Event object is passed, wait for it to be set after
            time-warping, and then clear it.
        """

        start_time = self.time.return_value
        end_time = start_time + jump
        while end_time >= self._next_wakeup:
            with self._time_lock:
                self.time.return_value = self._next_wakeup
            self.wake.break_wait()
            if wait_for_waitstate:
                wait_timeout = self.wake_waits.get(timeout=1)
            else:
                break

        if event_to_await:
            event_to_await.wait(1)
            event_to_await.clear()
        with self._time_lock:
            self.time.return_value = end_time


    def test_periodic(self):
        """Test reactor with periodic sampling."""
        period = 10.
        no_periods = 5
        period_strat = sampling.SamplePeriod(self.inform, self.sensor, period)
        self._add_strategy(period_strat)

        for i in range(no_periods):
            self.timewarp(period, event_to_await=self.inform_called)

        self.reactor.remove_strategy(period_strat)

        call_times = [t for t, vals in self.calls]
        self.assertEqual(len(self.calls), no_periods + 1)
        self.assertEqual(call_times,
                         [self.start_time + i*period
                          for i in range(no_periods + 1)])


    def test_event_rate(self):
        max_period = 10.
        min_period = 1.
        event_rate_strat = sampling.SampleEventRate(
            self.inform, self.sensor, min_period, max_period)
        self._add_strategy(event_rate_strat)

        no_max_periods = 3

        # Do some 'max period' updates where the sensor has not changed
        for i in range(no_max_periods):
            self.timewarp(max_period, event_to_await=self.inform_called)

        call_times = [t for t, vals in self.calls]
        self.assertEqual(len(self.calls), no_max_periods + 1)
        self.assertEqual(call_times,
                         [self.start_time + i*max_period
                          for i in range(no_max_periods + 1)])

        del self.calls[:]

        # Now do a sensor update without moving time along, should not result in
        # any additional updates
        update_time = self.time()
        expected_send_time = update_time + min_period
        self.sensor.set_value(2, self.sensor.NOMINAL, update_time)
        # There should, however, be a wake-wait event
        self.wake_waits.get(timeout=1)

        self.assertEqual(len(self.calls), 0)
        # Timewarp less than min update period, should not result in an inform
        # callback
        self.timewarp(min_period*0.6)

        # Move time beyond minimum step
        self.timewarp(min_period*1.01, event_to_await=self.inform_called)

        self.assertEqual(len(self.calls), 1)
        self.assertEqual(self.calls,
                         [(expected_send_time,
                           (self.sensor.name, update_time, 'nominal', '2'))])
        # Should not be any outstanding wake-waits
        self.assertEqual(self.wake_waits.qsize(), 0)

        del self.calls[:]
        # Time we expect the next max-period sample
        expected_send_time += max_period
        # Timewarp past the next expected max-period sample time
        self.timewarp(max_period + min_period*0.01,
                      event_to_await=self.inform_called)
        self.assertEqual(len(self.calls), 1)
        self.assertEqual(self.calls[0][0], expected_send_time)
        self.reactor._debug_now = False
        self.reactor.remove_strategy(event_rate_strat)


    def test_differential_rate(self):
        max_period = 10.
        min_period = 1.
        difference = 2
        differential_rate_strat = sampling.SampleDifferentialRate(
            self.inform, self.sensor, difference, min_period, max_period)
        self._add_strategy(differential_rate_strat)

        no_max_periods = 3

        # Do some 'max period' updates where the sensor has not changed
        for i in range(no_max_periods):
            self.timewarp(max_period, event_to_await=self.inform_called)

        call_times = [t for t, vals in self.calls]
        self.assertEqual(len(self.calls), no_max_periods + 1)
        self.assertEqual(call_times,
                         [self.start_time + i*max_period
                          for i in range(no_max_periods + 1)])

        del self.calls[:]

        # Now do a sensor update by more than `difference` without moving time
        # along, should not result in any additional updates
        update_time = self.time()
        expected_send_time = update_time + min_period
        # Intial value = 3, difference = 2, 3-2 = 1, but sensor must differ by
        # _more_ than difference, so choose 0
        self.sensor.set_value(0, self.sensor.NOMINAL, update_time)
        # There should, however, be a wake-wait event
        self.wake_waits.get(timeout=1)

        self.assertEqual(len(self.calls), 0)
        # Timewarp less than min update period, should not result in an inform
        # callback
        self.timewarp(min_period*0.6)
        self.assertEqual(len(self.calls), 0)

        # Move time beyond minimum step
        self.timewarp(min_period*1.01, event_to_await=self.inform_called)

        self.assertEqual(len(self.calls), 1)
        self.assertEqual(self.calls,
                         [(expected_send_time,
                           (self.sensor.name, update_time, 'nominal', '0'))])
        # Should not be any outstanding wake-waits
        self.assertEqual(self.wake_waits.qsize(), 0)

        del self.calls[:]

        # Do a sensor update by less than `difference`
        self.sensor.set_value(1, self.sensor.NOMINAL, update_time)
        # Time we expect the next max-period sample
        expected_send_time += max_period
        update_time = self.time()

        # Move time beyond minimum step, should not send an update, since the
        # sensor changed by less than `difference`
        self.timewarp(min_period*1.1)
        # Timewarp past the next expected max-period sample time
        self.timewarp(max_period - min_period,
                      event_to_await=self.inform_called)

        self.assertEqual(len(self.calls), 1)
        self.assertEqual(self.calls[0][0], expected_send_time)
        self.reactor._debug_now = False
        self.reactor.remove_strategy(differential_rate_strat)
Ejemplo n.º 19
0
class TestSampling(unittest.TestCase):
    def setUp(self):
        """Set up for test."""
        # test sensor
        self.sensor = DeviceTestSensor(katcp.Sensor.INTEGER,
                                       "an.int",
                                       "An integer.",
                                       "count", [-4, 3],
                                       timestamp=12345,
                                       status=katcp.Sensor.NOMINAL,
                                       value=3)

        # test callback
        def inform(msg):
            self.calls.append(msg)

        self.calls = []
        self.inform = inform

    def test_sampling(self):
        """Test getting and setting the sampling."""
        s = self.sensor

        sampling.SampleNone(None, s)
        sampling.SampleAuto(None, s)
        sampling.SamplePeriod(None, s, 10)
        sampling.SampleEvent(None, s)
        sampling.SampleDifferential(None, s, 2)
        self.assertRaises(ValueError, sampling.SampleNone, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleAuto, None, s, "bar")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s)
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "1.5")
        self.assertRaises(ValueError, sampling.SamplePeriod, None, s, "-1")
        self.assertRaises(ValueError, sampling.SampleEvent, None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s)
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s,
                          "-1")
        self.assertRaises(ValueError, sampling.SampleDifferential, None, s,
                          "1.5")

        sampling.SampleStrategy.get_strategy("none", None, s)
        sampling.SampleStrategy.get_strategy("auto", None, s)
        sampling.SampleStrategy.get_strategy("period", None, s, "15")
        sampling.SampleStrategy.get_strategy("event", None, s)
        sampling.SampleStrategy.get_strategy("differential", None, s, "2")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "random", None, s)
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "period", None, s, "foo")
        self.assertRaises(ValueError, sampling.SampleStrategy.get_strategy,
                          "differential", None, s, "bar")

    def test_event(self):
        """Test SampleEvent strategy."""
        event = sampling.SampleEvent(self.inform, self.sensor)
        self.assertEqual(self.calls, [])

        event.attach()
        self.assertEqual(len(self.calls), 1)

        self.sensor.set_value(2)
        self.assertEqual(len(self.calls), 2)

    def test_event_with_rate_limit(self):
        """Test SampleEvent strategy with a rate limit."""
        event = sampling.SampleEvent(self.inform, self.sensor, 100)
        self.assertEqual(self.calls, [])

        event.attach()
        self.assertEqual(len(self.calls), 1)

        for i in [-4, -3, -2, -1, 0, 1, 2, 3]:
            self.sensor.set_value(i)
        self.assertEqual(len(self.calls), 1)

        time.sleep(0.1)
        self.sensor.set_value(3)
        self.assertEqual(len(self.calls), 1)

    def test_differential(self):
        """Test SampleDifferential strategy."""
        diff = sampling.SampleDifferential(self.inform, self.sensor, 5)
        self.assertEqual(self.calls, [])

        diff.attach()
        self.assertEqual(len(self.calls), 1)

    def test_periodic(self):
        """Test SamplePeriod strategy."""
        # period = 10s
        period = sampling.SamplePeriod(self.inform, self.sensor, 10000)
        self.assertEqual(self.calls, [])

        period.attach()
        self.assertEqual(self.calls, [])

        period.periodic(1)
        self.assertEqual(len(self.calls), 1)

        period.periodic(11)
        self.assertEqual(len(self.calls), 2)

        period.periodic(12)
        self.assertEqual(len(self.calls), 3)