Esempio n. 1
0
class KnowledgeUpdateBehaviour(BehaviourBase):
    '''
    Behaviour that updates knowledge after it is activated
    '''
    def __init__(self,
                 name,
                 pattern,
                 new_tuple,
                 knowledge_base_name=KnowledgeBase.DEFAULT_NAME,
                 **kwargs):
        """
        :param name: behaviour name
        :param pattern: pattern match that will be updated
        :param new_tuple: new tuple value that will replaced the matched tuples
        :param knowledge_base_name: name of the knowledge base that is used
        :param kwargs: further general BehaviourBase arguments
        """

        super(KnowledgeUpdateBehaviour, self) \
            .__init__(name=name, **kwargs)

        self.pattern = pattern
        self.new_tuple = new_tuple

        self._kb_client = KnowledgeBaseClient(
            knowledge_base_name=knowledge_base_name)

    def start(self):
        rhbplog.logdebug("Updating knowledge '%s' to '%s'", self.pattern,
                         self.new_tuple)

        self._kb_client.update(self.pattern, self.new_tuple)
Esempio n. 2
0
class TestKnowledgeBaseSensor(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super(TestKnowledgeBaseSensor, self).__init__(*args, **kwargs)

        self.__knowledge_base_address = KnowledgeBase.DEFAULT_NAME + "KnowledgeBaseSensorTestSuite"

        # prevent influence of previous tests
        self.__message_prefix = 'TestKnowledgeBaseSensor' + str(time.time())

        self.start_kb_node()

        rospy.init_node('knowledge_sensor_test_node', log_level=rospy.DEBUG)
        self.__client = KnowledgeBaseClient(
            knowledge_base_name=self.__knowledge_base_address)

    def start_kb_node(self):
        """
        start the knowledge base node
        """
        package = 'knowledge_base'
        executable = 'knowledge_base_node.py'
        node = roslaunch.core.Node(package=package,
                                   node_type=executable,
                                   name=self.__knowledge_base_address,
                                   output='screen')
        launch = roslaunch.scriptapi.ROSLaunch()
        launch.start()
        self._kb_process = launch.launch(node)
        rospy.sleep(1)

    def test_basic(self):
        """
        Tests sensor output, if the fact is added at runtime (and did not exist before)
        """
        sensor = KnowledgeSensor(
            pattern=(self.__message_prefix, 'test_basic', 'pos', '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)
        rospy.sleep(0.1)
        sensor.sync()
        self.assertFalse(sensor.value)

        update_stamp = sensor._value_cache.update_time
        self.__client.push(
            (self.__message_prefix, 'test_basic', 'pos', '42', '0'))
        rospy.sleep(0.1)
        while update_stamp == sensor._value_cache.update_time:
            rospy.sleep(0.1)

        sensor.sync()
        self.assertTrue(sensor.value)

    def test_remove(self):
        """
        Tests sensor output , if the fact is removed
        """
        test_tuple = (self.__message_prefix, 'test_remove', 'pos', '42', '0')

        sensor = KnowledgeSensor(
            pattern=(self.__message_prefix, 'test_remove', 'pos', '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)
        rospy.sleep(0.1)
        update_stamp = sensor._value_cache.update_time
        self.__client.push(test_tuple)
        rospy.sleep(0.1)
        while update_stamp == sensor._value_cache.update_time:
            rospy.sleep(0.1)
        sensor.sync()
        self.assertTrue(sensor.value)

        update_stamp = sensor._value_cache.update_time

        removed_facts = self.__client.pop(test_tuple)

        self.assertIsNotNone(removed_facts, "No fact removed")

        while update_stamp == sensor._value_cache.update_time:
            rospy.sleep(0.1)

        sensor.sync()

        self.assertFalse(sensor.value)

    def test_knowledge_fact_int_sensor(self):
        """
        Test KnowledgeFactIntSensor
        """

        initial_value = 1337
        sensor_pattern = (self.__message_prefix,
                          'test_knowledge_fact_int_sensor', 'number', '*')
        sensor = KnowledgeFactNumberSensor(
            pattern=sensor_pattern,
            initial_value=initial_value,
            knowledge_base_name=self.__knowledge_base_address)
        rospy.sleep(0.1)

        sensor.sync()
        self.assertEquals(sensor.value, initial_value)

        test_value = 42

        update_stamp = sensor._value_cache.update_time
        # regular operation
        self.__client.push(
            (self.__message_prefix, 'test_knowledge_fact_int_sensor', 'number',
             str(test_value)))
        rospy.sleep(0.1)
        while update_stamp == sensor._value_cache.update_time:
            rospy.sleep(0.1)

        sensor.sync()
        self.assertEquals(sensor.value, test_value)

        update_stamp = sensor._value_cache.update_time
        # illegal operation with non integer value
        new_tuple = (self.__message_prefix, 'test_knowledge_fact_int_sensor',
                     'number', "NO_NUMBER")
        self.assertEquals(
            self.__client.update(pattern=sensor_pattern, new=new_tuple), True)
        rospy.sleep(0.1)
        while update_stamp > sensor.value_update_time:
            rospy.sleep(0.5)

        sensor.sync()
        rospy.loginfo(sensor.value)
        self.assertEquals(sensor.value, initial_value)

    def test_knowledge_fact_count_sensor(self):
        """
        Test KnowledgeFactCountSensor
        """

        initial_value = 0
        sensor_pattern = (self.__message_prefix,
                          'test_knowledge_fact_count_sensor', 'test', '*')
        sensor = KnowledgeFactCountSensor(
            pattern=sensor_pattern,
            knowledge_base_name=self.__knowledge_base_address,
            initial_value=initial_value)
        rospy.sleep(0.1)

        sensor.sync()
        self.assertEquals(sensor.value, initial_value)

        update_stamp = sensor._value_cache.update_time
        # regular operation
        self.__client.push((self.__message_prefix,
                            'test_knowledge_fact_count_sensor', 'test', 'a'))
        rospy.sleep(0.1)
        while update_stamp == sensor._value_cache.update_time:
            rospy.sleep(0.1)

        sensor.sync()
        self.assertEquals(sensor.value, 1)

        update_stamp = sensor._value_cache.update_time
        # regular operation
        self.__client.push((self.__message_prefix,
                            'test_knowledge_fact_count_sensor', 'test', 'b'))
        rospy.sleep(0.1)
        while update_stamp == sensor._value_cache.update_time:
            rospy.sleep(0.1)

        sensor.sync()
        self.assertEquals(sensor.value, 2)

        update_stamp = sensor._value_cache.update_time
        # regular operation
        self.__client.pop((self.__message_prefix,
                           'test_knowledge_fact_count_sensor', 'test', 'b'))
        rospy.sleep(0.1)
        while update_stamp == sensor._value_cache.update_time:
            rospy.sleep(0.1)

        sensor.sync()
        self.assertEquals(sensor.value, 1)
class TupleSpaceTestSuite(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super(TupleSpaceTestSuite, self).__init__(*args, **kwargs)

    def setUp(self):

        # prevent influence of previous tests
        self.__message_prefix = 'TupleSpaceTestSuite' + str(time.time())
        self.__knowledge_base_address = KnowledgeBase.DEFAULT_NAME

        #start KnowledgeBase
        package = 'knowledge_base'
        executable = 'knowledge_base_node.py'
        node = roslaunch.core.Node(package=package,
                                   node_type=executable,
                                   name=self.__knowledge_base_address)
        launch = roslaunch.scriptapi.ROSLaunch()
        launch.start()

        self._kb_process = launch.launch(node)

        rospy.init_node('TupleSpaceTestSuite', log_level=rospy.DEBUG)

        self.__client = KnowledgeBaseClient(self.__knowledge_base_address)

    def tearDown(self):
        self._kb_process.stop()

    def __wait_for_tuple(self, wait_for_it):
        """
        waits, until the requested tuple is contained in knowledge_base
        :param wait_for_it: tuple
        """
        while not self.__client.exists(wait_for_it):
            rospy.sleep(0.1)

    def __add_multiple_facts(self, *facts):
        for t in facts:
            self.__client.push(t)
        self.__wait_for_tuple(facts[len(facts) - 1])

    def __test_list_equality(self, l1, l2):
        error_message = 'Excepted "{0}", but is {1}'.format(str(l1), str(l2))
        self.assertEqual(len(l1), len(l2))
        for i1 in l1:
            self.assertTrue(i1 in l2, error_message)
        for i2 in l2:
            self.assertTrue(i2 in l1, error_message)

    def test_exists_for_non_existing(self):
        test_tuple = (self.__message_prefix, 'test_exists_for_non_existing',
                      '0', '0')
        self.assertFalse(self.__client.exists(test_tuple))

    def test_simple_adding(self):
        test_tuple = (self.__message_prefix, 'test_simple_adding', '0', '0')
        self.__client.push(test_tuple)
        self.assertTrue(self.__client.exists(test_tuple))

    def test_peek(self):
        test_tuple = (self.__message_prefix, 'test_peek', '0', '0')
        self.__client.push(test_tuple)
        self.assertEqual(test_tuple, self.__client.peek(test_tuple))

    def test_pop(self):
        test_tuple = (self.__message_prefix, 'test_pop', '0', '0')
        self.__client.push(test_tuple)
        self.assertTrue(self.__client.exists(test_tuple))

        removed = self.__client.pop(test_tuple)
        self.assertEqual(1, len(removed))
        self.assertEqual(test_tuple, removed[0])

    def test_remove_of_non_existing(self):
        tuples = []
        tuples.append(
            (self.__message_prefix, 'test_remove_of_non_existing', '0', '1'))
        tuples.append(
            (self.__message_prefix, 'test_remove_of_non_existing', '2', '5'))
        self.__add_multiple_facts(*tuples)

        self.__check_content(
            (self.__message_prefix, 'test_remove_of_non_existing', '*', '*'),
            *tuples)
        removed = self.__client.pop(
            (self.__message_prefix, 'test_remove_of_non_existing', '4', '*'))
        self.assertEqual(0, len(removed))
        self.__check_content(
            (self.__message_prefix, 'test_remove_of_non_existing', '*', '*'),
            *tuples)

    def test_multiple_pop(self):
        uninteresting_content = []
        uninteresting_content.append(
            (self.__message_prefix, 'test_multiple_pop', 'boring', '1'))
        uninteresting_content.append(
            (self.__message_prefix, 'test_multiple_pop', 'boring', '5'))
        to_remove = []
        to_remove.append(
            (self.__message_prefix, 'test_multiple_pop', 'interesting', '1'))
        to_remove.append(
            (self.__message_prefix, 'test_multiple_pop', 'interesting', '5'))
        all = []
        all.extend(uninteresting_content)
        all.extend(to_remove)
        self.__add_multiple_facts(*all)
        self.__check_content(
            (self.__message_prefix, 'test_multiple_pop', '*', '*'),
            *tuple(all))
        removed = self.__client.pop(
            (self.__message_prefix, 'test_multiple_pop', 'interesting', '*'))
        self.__test_list_equality(removed, to_remove)
        self.__check_content(
            (self.__message_prefix, 'test_multiple_pop', '*', '*'),
            *tuple(uninteresting_content))

    def test_placeholder(self):
        test_tuple = (self.__message_prefix, 'test_placeholder', '0', '0')
        pattern = (self.__message_prefix, 'test_placeholder', '*', '*')

        self.__client.push(test_tuple)
        self.assertEqual(test_tuple, self.__client.peek(pattern))

    def test_all(self):
        """
        test service for find all matching facts
        """
        tuples = []
        tuples.append((self.__message_prefix, 'test_all', 'pos', '0', '0'))
        tuples.append((self.__message_prefix, 'test_all', 'pos', '1', '0'))
        tuples.append((self.__message_prefix, 'test_all', 'pos', '1', '-4'))
        self.__add_multiple_facts(*tuples)

        self.__check_content(
            (self.__message_prefix, 'test_all', 'pos', '*', '*'),
            *tuple(tuples))

    def __check_content(self, pattern, *expected):
        """
        ensures, that exact the expected content is contained in the knowledge base
        :param pattern: filter for deciding, which contained facts of the kb should used
        :param expected: expected content of the kb
        """
        knowledge_base_content = self.__client.all(pattern)
        self.assertEqual(
            len(expected), len(knowledge_base_content), ' Expected: ' +
            str(expected) + ' but is ' + str(knowledge_base_content))
        for fact in expected:
            self.assertTrue(
                fact in knowledge_base_content, ' Expected: ' + str(expected) +
                ' but is ' + str(knowledge_base_content))

    def test_prevent_multiple_adding(self):
        """
        tests that no duplicates are contained in knowledge base
        """
        test_tuple = (self.__message_prefix, 'test_prevent_multiple_adding',
                      '0', '0')
        self.__client.push(test_tuple)
        self.__client.push(test_tuple)

        service_name = self.__knowledge_base_address + KnowledgeBase.ALL_SERVICE_NAME_POSTFIX
        rospy.wait_for_service(service_name)
        all_service = rospy.ServiceProxy(service_name, All)
        all_response = all_service(test_tuple)
        self.assertEqual(1, len(all_response.found))

    def __check_mocks_empty(self, update_mocks):
        """
        Asserts, that all given mocks has no remaining messages. All expected messages must be removed before method call
        :param update_mocks (list of UpdateSubscriberMock)
        """
        for update_mock in update_mocks:
            self.assertTrue(
                len(update_mock.add_msg) == 0,
                'Following mock has an unexpected add message received: ' +
                update_mock.name)
            self.assertTrue(
                len(update_mock.remove_msg) == 0,
                'Following mock has an unexpected remove message received: ' +
                update_mock.name)
            self.assertTrue(
                len(update_mock.update_msg) == 0,
                'Following mock has an unexpected update message received: ' +
                update_mock.name)

    def test_update_non_existing(self):
        """
        Tries to update a fact, which is not in the kb
        """
        prefix = self.__message_prefix + '_test_update_non_existing'
        new_fact = (prefix, 'new', '1')
        not_influenced = (prefix, 'not_influenced', '1')

        self.__client.push(not_influenced)

        informer_added = UpdateSubscriberMock(mock_name='added',
                                              client=self.__client,
                                              pattern=(prefix, 'new', '*'))
        informer_removed = UpdateSubscriberMock(mock_name='removed',
                                                client=self.__client,
                                                pattern=(prefix, 'unsuccess',
                                                         '*'))
        informer_updated = UpdateSubscriberMock(mock_name='updated',
                                                client=self.__client,
                                                pattern=(prefix, '*', '*'))
        informer_not_influenced = UpdateSubscriberMock(
            mock_name='not_influended',
            client=self.__client,
            pattern=(prefix, 'not_influenced', '*'))
        all_informers = [
            informer_added, informer_removed, informer_updated,
            informer_not_influenced
        ]

        self.assertFalse(
            self.__client.update((self.__message_prefix, 'unsuccess', '1'),
                                 new_fact,
                                 push_without_existing=False),
            'Old fact does not exist, but update was successfull')
        rospy.sleep(0.1)
        self.__check_mocks_empty(all_informers)
        self.__check_content((prefix, '*', '*'), not_influenced)

        self.assertTrue(
            self.__client.update((self.__message_prefix, 'not_existing', '1'),
                                 new_fact,
                                 push_without_existing=True),
            'Old fact does not exists and update was not successfull')
        rospy.sleep(0.1)

        self.assertEqual(
            1, len(informer_added.add_msg),
            'Error at receiving add message: ' +
            str(len(informer_added.add_msg)))
        self.assertEqual(new_fact, tuple(informer_added.add_msg[0].content))
        informer_added.add_msg = []

        self.assertEqual(
            1, len(informer_updated.add_msg),
            'Error at receiving add message: ' +
            str(len(informer_updated.add_msg)))
        self.assertEqual(new_fact, tuple(informer_updated.add_msg[0].content))
        informer_updated.add_msg = []

        self.__check_mocks_empty(all_informers)
        self.__check_content((prefix, '*', '*'), not_influenced, new_fact)

    def test_update_basic(self):
        """
        Tests simple update of an existing fact
        """
        prefix = self.__message_prefix + '_test_update_basic'
        old_fact = (prefix, 'old', '2')
        new_fact = (prefix, 'new', '1')
        not_influenced = (prefix, 'not_influenced', '1')

        self.__client.push(old_fact)
        self.__client.push(not_influenced)
        self.__check_content((prefix, '*', '*'), old_fact, not_influenced)

        informer_added = UpdateSubscriberMock(mock_name='added',
                                              client=self.__client,
                                              pattern=(prefix, 'new', '*'))
        informer_removed = UpdateSubscriberMock(mock_name='removed',
                                                client=self.__client,
                                                pattern=(prefix, 'old', '*'))
        informer_updated = UpdateSubscriberMock(mock_name='updated',
                                                client=self.__client,
                                                pattern=(prefix, '*', '*'))
        informer_second_target = UpdateSubscriberMock(
            mock_name='seccond',
            client=self.__client,
            pattern=(prefix, 'seccond_target', '*'))
        informer_not_influenced = UpdateSubscriberMock(
            mock_name='not_influended',
            client=self.__client,
            pattern=(prefix, 'not_influenced', '*'))
        all_informers = [
            informer_added, informer_removed, informer_updated,
            informer_second_target, informer_not_influenced
        ]

        self.assertTrue(self.__client.update(old_fact, new_fact),
                        'First update failed')
        rospy.sleep(0.1)

        self.assertEqual(
            1, len(informer_added.add_msg),
            'Error at receiving add message: ' +
            str(len(informer_added.add_msg)))
        self.assertEqual(new_fact, tuple(informer_added.add_msg[0].content))
        informer_added.add_msg = []

        self.assertEqual(
            1, len(informer_removed.remove_msg),
            'Error at receiving remove message: ' +
            str(len(informer_removed.remove_msg)))
        self.assertEqual(old_fact, tuple(informer_removed.remove_msg[0].fact))
        informer_removed.remove_msg = []

        self.assertEqual(
            1, len(informer_updated.update_msg),
            'Error at receiving update message: ' +
            str(len(informer_updated.update_msg)))
        self.assertEqual(1, len(informer_updated.update_msg[0].removed))
        self.assertEqual(
            old_fact, tuple(informer_updated.update_msg[0].removed[0].content))
        self.assertEqual(new_fact, tuple(informer_updated.update_msg[0].new))
        informer_updated.update_msg = []

        self.__check_mocks_empty(all_informers)
        self.__check_content((prefix, '*', '*'), new_fact, not_influenced)

    def test_update_existing_target(self):
        """
        Tests update of an existing fact to a fact, which is already contained in the kb
        """
        prefix = self.__message_prefix + '_test_update_existing_target'
        old_fact = (prefix, 'old', '2')
        new_fact = (prefix, 'new', '1')
        not_influenced = (prefix, 'not_influenced', '1')

        self.__add_multiple_facts(old_fact, new_fact, not_influenced)
        self.__check_content((prefix, '*', '*'), old_fact, new_fact,
                             not_influenced)

        informer_added = UpdateSubscriberMock(mock_name='added',
                                              client=self.__client,
                                              pattern=(prefix, 'new', '*'))
        informer_removed = UpdateSubscriberMock(mock_name='removed',
                                                client=self.__client,
                                                pattern=(prefix, 'old', '*'))
        informer_updated = UpdateSubscriberMock(mock_name='updated',
                                                client=self.__client,
                                                pattern=(prefix, '*', '*'))
        informer_second_target = UpdateSubscriberMock(
            mock_name='seccond',
            client=self.__client,
            pattern=(prefix, 'seccond_target', '*'))
        informer_not_influenced = UpdateSubscriberMock(
            mock_name='not_influended',
            client=self.__client,
            pattern=(prefix, 'not_influenced', '*'))
        all_informers = [
            informer_added, informer_removed, informer_updated,
            informer_second_target, informer_not_influenced
        ]

        self.assertTrue(self.__client.update(old_fact, new_fact),
                        'update failed')
        rospy.sleep(0.1)

        self.assertEqual(
            1, len(informer_removed.remove_msg),
            'Error at receiving remove message: ' +
            str(len(informer_removed.remove_msg)))
        self.assertEqual(old_fact, tuple(informer_removed.remove_msg[0].fact))
        informer_removed.remove_msg = []
        self.assertEqual(
            1, len(informer_updated.remove_msg),
            'Error at receiving second remove message: ' +
            str(len(informer_removed.remove_msg)))
        self.assertEqual(old_fact, tuple(informer_updated.remove_msg[0].fact))
        informer_updated.remove_msg = []

        self.__check_mocks_empty(all_informers)
        self.__check_content((prefix, '*', '*'), new_fact, not_influenced)

    def test_replace_several_facts(self):
        prefix = self.__message_prefix + '_test_replace_several_facts'
        old_fact_1 = (prefix, 'old', '2')
        old_fact_2 = (prefix, 'old', '3')
        not_influenced = (prefix, 'not_influenced', '1')
        self.__add_multiple_facts(old_fact_1, old_fact_2, not_influenced)
        self.__check_content((prefix, '*', '*'), old_fact_1, old_fact_2,
                             not_influenced)

        new_fact = (prefix, 'new', '1')

        informer_added = UpdateSubscriberMock(mock_name='added',
                                              client=self.__client,
                                              pattern=(prefix, 'new', '*'))
        informer_removed = UpdateSubscriberMock(mock_name='removed',
                                                client=self.__client,
                                                pattern=(prefix, 'old', '*'))
        informer_updated = UpdateSubscriberMock(mock_name='updated',
                                                client=self.__client,
                                                pattern=(prefix, '*', '*'))
        informer_not_influenced = UpdateSubscriberMock(
            mock_name='not_influended',
            client=self.__client,
            pattern=(prefix, 'not_influenced', '*'))
        all_informers = [
            informer_added, informer_removed, informer_updated,
            informer_not_influenced
        ]

        self.assertTrue(self.__client.update((prefix, 'old', '*'), new_fact),
                        'update failed')
        rospy.sleep(0.1)

        self.__check_content((prefix, '*', '*'), new_fact, not_influenced)

        self.assertEqual(
            1, len(informer_added.add_msg),
            'Error at receiving add message: ' +
            str(len(informer_added.add_msg)))
        self.assertEqual(new_fact, tuple(informer_added.add_msg[0].content))
        informer_added.add_msg = []

        self.assertEqual(
            2, len(informer_removed.remove_msg),
            'Error at receiving remove messages: ' +
            str(len(informer_removed.remove_msg)))
        removed_facts = [old_fact_1, old_fact_2]
        for remove_msg in informer_removed.remove_msg:
            self.assertTrue(
                tuple(remove_msg.fact) in removed_facts,
                str(remove_msg.fact) + ' not in ' + str(removed_facts))
            removed_facts.remove(tuple(remove_msg.fact))
        informer_removed.remove_msg = []

        self.assertEqual(
            1, len(informer_updated.update_msg),
            'Error at receiving update message: ' +
            str(len(informer_updated.update_msg)))
        self.assertEqual(tuple(informer_updated.update_msg[0].new), new_fact)
        removed_facts = [old_fact_1, old_fact_2]
        for removed_fact in informer_updated.update_msg[0].removed:
            self.assertTrue(
                tuple(removed_fact.content) in removed_facts,
                str(tuple(removed_fact.content)) + ' not in ' +
                str(removed_facts))
            removed_facts.remove(tuple(removed_fact.content))
        informer_updated.update_msg = []

        self.__check_mocks_empty(all_informers)
Esempio n. 4
0
class UpdateHandlerTestSuite(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super(UpdateHandlerTestSuite, self).__init__(*args, **kwargs)

    def setUp(self):

        self.__knowledge_base_address = KnowledgeBase.DEFAULT_NAME
        # prevent influence of previous tests
        self.__message_prefix = 'UpdateHandlerTestSuite' + str(time.time())

        # start KnowledgeBase
        self.start_kb_node()

        rospy.init_node('UpdateHandlerTestSuite', log_level=rospy.DEBUG)

        self.__client = KnowledgeBaseClient(self.__knowledge_base_address)

    def start_kb_node(self):
        """
        start the knowledge base node
        """
        package = 'knowledge_base'
        executable = 'knowledge_base_node.py'
        node = roslaunch.core.Node(package=package,
                                   node_type=executable,
                                   name=self.__knowledge_base_address)
        launch = roslaunch.scriptapi.ROSLaunch()
        launch.start()
        self._kb_process = launch.launch(node)

    def tearDown(self):
        self._kb_process.stop()

    def test_simple_adding(self):
        """
        Tests basic case of adding a tuple
        """
        test_tuple = (self.__message_prefix, 'test_simple_adding', '0', '0')

        cache = KnowledgeBaseFactCache(pattern=test_tuple)
        self.assertFalse(cache.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')

        self.__client.push(test_tuple)
        rospy.sleep(0.1)

        self.assertTrue(cache.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')

    def test_remove(self):
        test_tuple = (self.__message_prefix, 'test_remove', '0', '0')

        cache = KnowledgeBaseFactCache(pattern=test_tuple)
        self.assertFalse(cache.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')

        self.__client.push(test_tuple)
        rospy.sleep(0.1)

        self.assertTrue(cache.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')

        self.__client.pop(test_tuple)

        rospy.sleep(0.1)
        self.assertFalse(
            cache.does_fact_exists(),
            'Tuple was removed, but is still indicated as present')

    def test_multiple_caches_for_same_pattern(self):
        test_tuple = (self.__message_prefix,
                      'test_multiple_caches_for_same_pattern', '0', '0')

        cache1 = KnowledgeBaseFactCache(pattern=test_tuple)
        cache2 = KnowledgeBaseFactCache(pattern=test_tuple)

        self.assertFalse(cache1.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')
        self.assertFalse(cache2.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')

        self.__client.push(test_tuple)
        rospy.sleep(0.1)

        self.assertTrue(cache1.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')
        self.assertTrue(cache2.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')

    def test_middle_placeholder(self):
        """
        Test updating an existing fact 
        """
        prefix = self.__message_prefix + '_test_middle_placeholder'

        first_fact = (prefix, 'updated', 'a', '1')

        second_fact = (prefix, 'updated', 'b', '1')

        self.__client.push(first_fact)
        self.__client.push(second_fact)

        cache = KnowledgeBaseFactCache(pattern=(prefix, 'updated', '*', '1'))

        rospy.sleep(0.1)

        current = cache.get_all_matching_facts()

        self.assertEqual(2, len(current))
        self.assertTrue(first_fact in current, 'fact not found')
        self.assertTrue(second_fact in current, 'fact not found')

    def test_update_existing(self):
        """
        Test updating an existing fact 
        """
        prefix = self.__message_prefix + '_test_update_empty'

        updated_old = (prefix, 'updated', '1')

        self.__client.push(updated_old)

        cache = KnowledgeBaseFactCache(pattern=(prefix, '*', '*'))

        updated_new = (prefix, 'updated', '1')

        self.__client.update((prefix, '*', '*'), updated_new)
        rospy.sleep(0.1)

        current = cache.get_all_matching_facts()

        self.assertEqual(1, len(current))
        self.assertTrue(updated_new in current, 'Update not noticed')

    def test_update(self):
        prefix = self.__message_prefix + '_test_update'
        updated_old = (prefix, 'updated', '1')
        not_influenced = (prefix, 'not_influenced', '1')
        self.__client.push(updated_old)
        self.__client.push(not_influenced)

        cache = KnowledgeBaseFactCache(pattern=(prefix, '*', '*'))

        updated_new = (prefix, 'updated', '0')

        self.__client.update(updated_old, updated_new)
        rospy.sleep(0.1)

        current = cache.get_all_matching_facts()
        self.assertEqual(2, len(current))
        self.assertTrue(updated_new in current, 'Update not noticed')
        self.assertTrue(not_influenced in current,
                        'NotInfluenced was influenced')

    def test_multiple_updates(self):
        prefix = self.__message_prefix + '_test_multiple_updates'
        updated_old_1 = (prefix, 'fact_1', '1')
        updated_old_2 = (prefix, 'fact_2', '2')
        self.__client.push(updated_old_1)
        self.__client.push(updated_old_2)

        cache = KnowledgeBaseFactCache(pattern=(prefix, '*', '*'))

        updated_new_1 = (prefix, 'fact_1', '3')
        self.__client.update(updated_old_1, updated_new_1)

        updated_new_2 = (prefix, 'fact_2', '4')
        self.__client.update(updated_old_2, updated_new_2)
        rospy.sleep(0.1)

        current = cache.get_all_matching_facts()
        self.assertEqual(2, len(current))
        self.assertTrue(updated_new_1 in current, 'Update not noticed')
        self.assertTrue(updated_new_2 in current, 'Update not noticed')

    def __check_content(self, fact_cache, *expected_facts):

        content = fact_cache.get_all_matching_facts()
        error_message = 'Excepted "{0}", but is {1}'.format(
            str(expected_facts), str(content))
        self.assertEqual(len(content), len(expected_facts), error_message)
        for expected_fact in expected_facts:
            self.assertTrue(expected_fact in content, error_message)
        for existing_fact in content:
            self.assertTrue(existing_fact in expected_facts, error_message)

    def test_update_replacing_several_facts(self):

        prefix = self.__message_prefix + 'test_update_replacing_several_facts'
        updated_old_1 = (prefix, 'toUpdate', '1')
        updated_old_2 = (prefix, 'toUpdate', '2')
        not_influenced = (prefix, 'not_influenced', '1')

        cache = KnowledgeBaseFactCache(pattern=(prefix, '*', '*'))

        rospy.sleep(0.1)

        self.__client.push(updated_old_1)
        self.__client.push(updated_old_2)
        self.__client.push(not_influenced)

        rospy.sleep(0.1)

        self.__check_content(cache, updated_old_1, updated_old_2,
                             not_influenced)

        new_fact = (prefix, 'updated', '3')

        self.__client.update((prefix, 'toUpdate', '*'), new_fact)

        rospy.sleep(0.1)

        self.__check_content(cache, not_influenced, new_fact)
class FactTableModel(QAbstractTableModel):
    """
    Simple QT table model for Knowledge Base facts
    """
    def __init__(self, kb_name, parent=None, *args):
        super(FactTableModel, self).__init__(parent)
        self.facts = None
        self._kb_name = kb_name
        self._kb_client = KnowledgeBaseClient(
            knowledge_base_name=self._kb_name, timeout=0.5)

    def update(self, new_facts):
        """
        Use the method to update the fact/data base
        :param new_facts: new list of fact tuples
        """
        if new_facts:
            new_facts.sort()
        self.layoutAboutToBeChanged.emit()
        self.facts = new_facts
        self.layoutChanged.emit()

    def clear(self):
        """
        Clear current fact base
        """
        self.update(None)

    def rowCount(self, parent=QModelIndex()):
        if self.facts:
            return len(self.facts)
        else:
            return 0

    def columnCount(self, parent=QModelIndex()):

        if self.facts:
            max_column = 0
            for fact in self.facts:
                max_column = max(max_column, len(fact))
            return max_column
        else:
            return 0

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole or role == Qt.EditRole:
            i = index.row()
            j = index.column()
            if j < len(self.facts[i]):
                return '{0}'.format(self.facts[i][j])
            else:
                return QVariant()
        else:
            return QVariant()

    def setData(self, index, value, role):
        """
        Update remote knowledge base data
        """
        try:

            pattern = copy.deepcopy(self.facts[index.row()])

            current_fact = list(pattern)

            if index.column() >= len(current_fact):
                for i in range(0, index.column() - len(current_fact) + 1):
                    current_fact.append("")
            current_fact[index.column()] = str(value)

            rospy.logdebug("Changing knowledge: \n" + str(pattern) +
                           "\n --to-- \n" + str(current_fact))

            return self._kb_client.update(pattern=pattern, new=current_fact)
        except Exception:
            rospy.logerr(traceback.format_exc())
            return False

    def flags(self, index):
        return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable

    @property
    def kb_name(self):
        return self._kb_name

    @kb_name.setter
    def kb_name(self, value):
        if self._kb_name != value:
            self._kb_name = value
            self._kb_client = KnowledgeBaseClient(knowledge_base_name=value,
                                                  timeout=0.5)
class UpdateHandlerTestSuite(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super(UpdateHandlerTestSuite, self).__init__(*args, **kwargs)

    def setUp(self):

        self.__knowledge_base_address = KnowledgeBase.DEFAULT_NAME + "UpdateHandlerTestSuite"
        # prevent influence of previous tests
        self.__message_prefix = 'UpdateHandlerTestSuite' + str(time.time())

        # start KnowledgeBase
        self.start_kb_node()

        rospy.init_node('UpdateHandlerTestSuite', log_level=rospy.DEBUG)

        self.__client = KnowledgeBaseClient(self.__knowledge_base_address)

    def start_kb_node(self):
        """
        start the knowledge base node
        """
        package = 'knowledge_base'
        executable = 'knowledge_base_node.py'
        node = roslaunch.core.Node(package=package,
                                   node_type=executable,
                                   name=self.__knowledge_base_address,
                                   output='screen')
        launch = roslaunch.scriptapi.ROSLaunch()
        launch.start()
        self._kb_process = launch.launch(node)

    def tearDown(self):
        self._kb_process.stop()

    def test_simple_adding(self):
        """
        Tests basic case of adding a tuple
        """
        test_tuple = (self.__message_prefix, 'test_simple_adding', '0', '0')

        cache = KnowledgeBaseFactCache(
            pattern=test_tuple,
            knowledge_base_name=self.__knowledge_base_address)
        self.assertFalse(cache.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')

        self.__client.push(test_tuple)
        rospy.sleep(0.1)

        self.assertTrue(cache.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')

    def test_remove(self):
        test_tuple = (self.__message_prefix, 'test_remove', '0', '0')

        cache = KnowledgeBaseFactCache(
            pattern=test_tuple,
            knowledge_base_name=self.__knowledge_base_address)
        self.assertFalse(cache.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')

        self.__client.push(test_tuple)
        rospy.sleep(0.1)

        self.assertTrue(cache.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')

        self.__client.pop(test_tuple)

        rospy.sleep(0.1)
        self.assertFalse(
            cache.does_fact_exists(),
            'Tuple was removed, but is still indicated as present')

    def test_multiple_caches_for_same_pattern(self):
        test_tuple = (self.__message_prefix,
                      'test_multiple_caches_for_same_pattern', '0', '0')

        cache1 = KnowledgeBaseFactCache(
            pattern=test_tuple,
            knowledge_base_name=self.__knowledge_base_address)
        cache2 = KnowledgeBaseFactCache(
            pattern=test_tuple,
            knowledge_base_name=self.__knowledge_base_address)

        self.assertFalse(cache1.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')
        self.assertFalse(cache2.does_fact_exists(),
                         'Tuple is not added, but is indicated as present')

        self.__client.push(test_tuple)
        rospy.sleep(0.1)

        self.assertTrue(cache1.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')
        self.assertTrue(cache2.does_fact_exists(),
                        'Tuple was added, but is not indicated as present')

    def test_middle_placeholder(self):
        """
        Test updating an existing fact
        """
        prefix = self.__message_prefix + '_test_middle_placeholder'

        first_fact = (prefix, 'updated', 'a', '1')

        second_fact = (prefix, 'updated', 'b', '1')

        self.__client.push(first_fact)
        self.__client.push(second_fact)
        pattern = (prefix, 'updated', '*', '1')
        cache = KnowledgeBaseFactCache(
            pattern=pattern, knowledge_base_name=self.__knowledge_base_address)

        rospy.sleep(0.1)

        current = cache.get_all_matching_facts()

        self.assertEqual(2, len(current))
        self.assertTrue(first_fact in current, 'fact not found')
        self.assertTrue(second_fact in current, 'fact not found')

    def test_update_existing(self):
        """
        Test updating a fact that is already stored.
        """
        prefix = self.__message_prefix + '_test_update_existing'

        updated_old = (prefix, 'updated', '1')

        self.assertTrue(self.__client.push(updated_old))

        rospy.sleep(0.1)

        cache = KnowledgeBaseFactCache(
            pattern=(prefix, '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)
        rospy.sleep(0.1)
        updated_new = (prefix, 'updated', '1')
        self.assertTrue(self.__client.update((prefix, '*', '*'), updated_new))

        rospy.sleep(
            0.5
        )  # don' t wait for updates because we will not get one, as nothing changed

        current = cache.get_all_matching_facts()

        self.assertEqual(1, len(current))
        self.assertTrue(updated_new in current, 'Update corrupted')

    def test_update(self):
        prefix = self.__message_prefix + '_test_update'
        updated_old = (prefix, 'updated', '1')
        not_influenced = (prefix, 'not_influenced', '1')
        self.__client.push(updated_old)
        self.__client.push(not_influenced)

        cache = KnowledgeBaseFactCache(
            pattern=(prefix, '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        update_stamp = cache.update_time

        updated_new = (prefix, 'updated', '0')

        self.__client.update(updated_old, updated_new)
        while update_stamp == cache.update_time:
            rospy.sleep(0.5)

        current = cache.get_all_matching_facts()
        self.assertEqual(2, len(current))
        self.assertTrue(updated_new in current, 'Update not noticed')
        self.assertTrue(not_influenced in current,
                        'NotInfluenced was influenced')

    def test_update_non_existing(self):
        prefix = self.__message_prefix + 'test_update_non_existing'
        updated_not_existing = (prefix, 'updated', '1')

        cache = KnowledgeBaseFactCache(
            pattern=(prefix, '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        update_stamp = cache.update_time

        updated_new = (prefix, 'updated', '0')

        self.__client.update(updated_not_existing, updated_new)
        while update_stamp == cache.update_time:
            rospy.sleep(0.5)

        current = cache.get_all_matching_facts()
        self.assertEqual(1, len(current))
        self.assertTrue(updated_new in current, 'Update not noticed')

    def test_multiple_updates(self):
        prefix = self.__message_prefix + '_test_multiple_updates'
        updated_old_1 = (prefix, 'fact_1', '1')
        updated_old_2 = (prefix, 'fact_2', '2')
        self.__client.push(updated_old_1)
        self.__client.push(updated_old_2)

        cache = KnowledgeBaseFactCache(
            pattern=(prefix, '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        updated_new_1 = (prefix, 'fact_1', '1*')
        self.__client.update(updated_old_1, updated_new_1)

        updated_new_2 = (prefix, 'fact_2', '2*')

        update_stamp = cache.update_time
        self.__client.update(updated_old_2, updated_new_2)

        rospy.sleep(0.5)
        while update_stamp == cache.update_time:
            rospy.sleep(0.5)

        current = cache.get_all_matching_facts()

        self.assertEqual(2, len(current))
        self.assertTrue(updated_new_1 in current,
                        'Update of updated_new_1 not noticed')
        self.assertTrue(updated_new_2 in current,
                        'Update of updated_new_2 not noticed')

    def test_update_with_partly_matching_patterns(self):
        """
        test an update that replaces several existing facts with a single new one or adds a new fact as a result of an
        update
        """
        prefix = self.__message_prefix + 'test_update_reduction'
        updated_old_1 = (prefix, 'fact_1', '1')
        updated_old_2 = (prefix, 'fact_2', '1')
        self.__client.push(updated_old_1)
        self.__client.push(updated_old_2)

        cache_all = KnowledgeBaseFactCache(
            pattern=(prefix, '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        cache_specific_1 = KnowledgeBaseFactCache(
            pattern=(prefix, 'fact_1', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        cache_specific_2 = KnowledgeBaseFactCache(
            pattern=(prefix, 'fact_2', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        cache_specific_3 = KnowledgeBaseFactCache(
            pattern=(prefix, 'fact_3', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        current_all = cache_all.get_all_matching_facts()
        current_specific_1 = cache_specific_1.get_all_matching_facts()
        current_specific_2 = cache_specific_2.get_all_matching_facts()
        current_specific_3 = cache_specific_3.get_all_matching_facts()

        self.assertEqual(2, len(current_all))
        self.assertEqual(1, len(current_specific_1))
        self.assertEqual(1, len(current_specific_2))
        self.assertEqual(0, len(current_specific_3))

        updated_new = (prefix, 'fact_3', '1')
        update_stamp1 = cache_all.update_time
        update_stamp2 = cache_specific_1.update_time
        update_stamp3 = cache_specific_2.update_time
        update_stamp4 = cache_specific_3.update_time
        self.__client.update((prefix, '*', '1'), updated_new)

        # wait until the caches are updated
        rospy.sleep(0.5)
        while update_stamp1 == cache_all.update_time or \
                update_stamp2 == cache_specific_1.update_time or update_stamp3 == cache_specific_2.update_time \
                or update_stamp4 == cache_specific_3.update_time:
            rospy.sleep(0.1)

        current_all = cache_all.get_all_matching_facts()
        current_specific_1 = cache_specific_1.get_all_matching_facts()
        current_specific_2 = cache_specific_2.get_all_matching_facts()
        current_specific_3 = cache_specific_3.get_all_matching_facts()

        self.assertEqual(1, len(current_all))
        self.assertEqual(0, len(current_specific_1))
        self.assertEqual(0, len(current_specific_2))
        self.assertEqual(1, len(current_specific_3))
        self.assertTrue(updated_new in current_all,
                        'Update general cache of not noticed')
        self.assertTrue(updated_new not in current_specific_1,
                        'Delete update in specific cache 1 not noticed')
        self.assertTrue(updated_new not in current_specific_2,
                        'Delete update in specific cache 2 not noticed')
        self.assertTrue(updated_new in current_specific_3,
                        'Add update in specific cache 3 not noticed')

    def test_all_pattern_with_update(self):
        prefix = self.__message_prefix + '_test_multiple_updates'
        updated_old_1 = (prefix, 'fact_1', '1')
        self.__client.push(updated_old_1)

        all_pattern = ()
        cache = KnowledgeBaseFactCache(
            pattern=all_pattern,
            knowledge_base_name=self.__knowledge_base_address)
        update_stamp = cache.update_time

        updated_new_1 = (prefix, 'fact_1', '3')
        self.__client.update(updated_old_1, updated_new_1)

        while update_stamp == cache.update_time:
            rospy.sleep(0.1)

        current = cache.get_all_matching_facts()
        self.assertEqual(1, len(current))
        self.assertTrue(updated_new_1 in current, 'Update not noticed')

    def __check_content(self, fact_cache, *expected_facts):

        content = fact_cache.get_all_matching_facts()
        error_message = 'Excepted "{0}", but is {1}'.format(
            str(expected_facts), str(content))
        self.assertEqual(len(content), len(expected_facts), error_message)
        for expected_fact in expected_facts:
            self.assertTrue(expected_fact in content, error_message)
        for existing_fact in content:
            self.assertTrue(existing_fact in expected_facts, error_message)

    def test_update_replacing_several_facts(self):

        prefix = self.__message_prefix + 'test_update_replacing_several_facts'
        updated_old_1 = (prefix, 'toUpdate', '1')
        updated_old_2 = (prefix, 'toUpdate', '2')
        not_influenced = (prefix, 'not_influenced', '1')

        cache = KnowledgeBaseFactCache(
            pattern=(prefix, '*', '*'),
            knowledge_base_name=self.__knowledge_base_address)

        rospy.sleep(0.1)

        self.__client.push(updated_old_1)
        self.__client.push(updated_old_2)
        self.__client.push(not_influenced)

        rospy.sleep(0.1)

        self.__check_content(cache, updated_old_1, updated_old_2,
                             not_influenced)

        new_fact = (prefix, 'updated', '3')

        self.__client.update((prefix, 'toUpdate', '*'), new_fact)

        rospy.sleep(0.1)

        self.__check_content(cache, not_influenced, new_fact)

    def test_mass_updates(self):
        """
        Here we test many updates in a short time period
        """

        prefix = self.__message_prefix + 'test_mass_updates'

        number_of_entries = 500

        # fill KB
        for i in range(number_of_entries):

            updated_old_1 = (prefix, 'fact', str(i))
            self.__client.push(updated_old_1)

        cache = KnowledgeBaseFactCache(
            pattern=(prefix, 'fact_new', '*'),
            knowledge_base_name=self.__knowledge_base_address)
        current = cache.get_all_matching_facts()
        self.assertEqual(0, len(current))

        rospy.sleep(0.5)

        for i in range(number_of_entries):
            updated_old_1 = (prefix, 'fact', str(i))
            updated_new_1 = (prefix, 'fact_new', str(i))
            last_fact = updated_new_1
            self.__client.update(updated_old_1, updated_new_1)

        rospy.sleep(0.5)
        while last_fact != cache.last_updated_fact:
            rospy.sleep(0.5)

        current = cache.get_all_matching_facts()

        # test if we received all tuples
        for i in range(number_of_entries):
            updated_new_1 = (prefix, 'fact_new', str(i))
            self.assertTrue(
                updated_new_1 in current,
                'Update of "' + str(updated_new_1) + '" not noticed')

    def test_sub_fact_matching(self):
        prefix = self.__message_prefix + '_test_multiple_updates'
        fact_1 = (prefix, 'fact_1', '1', 'a')
        self.__client.push(fact_1)
        fact_2 = (prefix, 'fact_2', '2', 'a')
        self.__client.push(fact_2)
        fact_3 = (prefix, 'fact_3', '3', 'b')
        self.__client.push(fact_3)

        all_pattern = ()
        cache = KnowledgeBaseFactCache(
            pattern=all_pattern,
            knowledge_base_name=self.__knowledge_base_address)

        # initial check to see if we have a correct setup
        current = cache.get_all_matching_facts()
        self.assertEqual(3, len(current))

        sub_fact_pattern = (prefix, 'fact_2', '2', 'a')
        does_exist = cache.does_sub_fact_exist(pattern=sub_fact_pattern)

        self.assertTrue(does_exist)

        first_sub_fact = cache.get_matching_sub_fact(pattern=sub_fact_pattern)

        self.assertEqual(first_sub_fact, fact_2)

        # test with sub placeholders
        sub_fact_pattern = (prefix, '*', '*', 'a')
        does_exist = cache.does_sub_fact_exist(pattern=sub_fact_pattern)

        self.assertTrue(does_exist)

        first_sub_fact = cache.get_matching_sub_fact(pattern=sub_fact_pattern)

        self.assertEqual(first_sub_fact, fact_1)

        all_sub_facts = cache.get_all_matching_sub_facts(
            pattern=sub_fact_pattern)
        self.assertEqual(2, len(all_sub_facts))
        self.assertTrue(fact_1 in all_sub_facts,
                        "fact_1 not found in sub facts")
        self.assertTrue(fact_2 in all_sub_facts,
                        "fact_2 not found in sub facts")

        # invalid pattern
        sub_fact_pattern = (prefix, 'fact_unknown', '2', 'a')
        does_exist = cache.does_sub_fact_exist(pattern=sub_fact_pattern)

        self.assertFalse(does_exist)