[asterisk-commits] jbigelow: testsuite/asterisk/trunk r3783 - in /asterisk/trunk: configs/ lib/p...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri May 17 15:53:19 CDT 2013


Author: jbigelow
Date: Fri May 17 15:53:14 2013
New Revision: 3783

URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=3783
Log:
Add pluggable CEL verification module

This uses AMI to allow verification of CEL messages. It can be configured to
allow CEL messages to come before and/or after others if they are not always
in the same order due to channels operating on their own threads of execution.

(issue ASTERISK-21422)
(closes issue ASTERISK-21422)

Review: https://reviewboard.asterisk.org/r/2498/


Modified:
    asterisk/trunk/configs/cel.conf
    asterisk/trunk/lib/python/asterisk/ami.py
    asterisk/trunk/sample-yaml/ami-config.yaml.sample

Modified: asterisk/trunk/configs/cel.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/configs/cel.conf?view=diff&rev=3783&r1=3782&r2=3783
==============================================================================
--- asterisk/trunk/configs/cel.conf (original)
+++ asterisk/trunk/configs/cel.conf Fri May 17 15:53:14 2013
@@ -2,3 +2,6 @@
 enable=yes
 apps=dial,park,queue
 events=ALL
+
+[manager]
+enabled=yes

Modified: asterisk/trunk/lib/python/asterisk/ami.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/lib/python/asterisk/ami.py?view=diff&rev=3783&r1=3782&r2=3783
==============================================================================
--- asterisk/trunk/lib/python/asterisk/ami.py (original)
+++ asterisk/trunk/lib/python/asterisk/ami.py Fri May 17 15:53:14 2013
@@ -43,6 +43,16 @@
             self.count_max = float("inf")
 
         self.event_count = 0
+
+        if instance_config['type'] == 'cel':
+            # If the type is 'cel' and no condition matches are defined in the
+            # test's yaml then create the dict with setting the Event to 'CEL'.
+            # Otherwise set Event to 'CEL' since it's the only Event we want.
+            if instance_config['conditions']['match'] is None:
+                instance_config['conditions']['match'] = {'Event' : 'CEL'}
+                self.match_conditions = instance_config['conditions']['match']
+            else:
+                instance_config['conditions']['match']['Event'] = 'CEL'
 
         if 'Event' not in self.match_conditions:
             logger.error("No event specified to match on. Aborting test")
@@ -231,6 +241,390 @@
         self.test_object.set_passed(self.passed)
         return callback_param
 
+class AMICel(AMIEventInstance):
+    '''
+    A subclass of AMIEventInstance that operates by matching headers of
+    AMI CEL events to expected values. This is similar to
+    AMIOrderedHeaderMatchInstance but differs in that it's specifically for
+    checking CEL events and that a partial order may be specified to allow some
+    events to be out of order.
+    '''
+    def __init__(self, instance_config, test_object):
+        super(AMICel, self).__init__(instance_config, test_object)
+        self.match_index = 0
+        self.submatch_index = None
+        self.cel_ids = []
+        self.match_requirements = []
+        self.partial_order_requirements = []
+        self.cel_eventnames = []
+        self.matched_events = []
+        self.marked_failed = False
+
+        for instance in instance_config['requirements']:
+            if 'id' not in instance:
+                logger.error("No 'id' keyword was found for a cel requirement "
+                        "in the test's yaml file.")
+                raise Exception
+
+            if not instance['id']:
+                logger.error("No value found for the 'id' keyword of a cel "
+                        "requirement in the test's yaml file.")
+                raise Exception
+
+            if instance['id'] in self.cel_ids:
+                logger.error("The cel id '%s' is a duplicate. ID's must be "
+                        "unique." % instance['id'])
+                raise Exception
+
+            self.cel_ids.append(instance['id'])
+
+            self.match_requirements.append(
+                    instance.get('match', {}))
+
+            if 'partialorder' not in instance:
+                self.partial_order_requirements.append(
+                        instance.get('partialorder',
+                            {'after' : None, 'before' : None}))
+            else:
+                tmp = {}
+                origpartorder = instance['partialorder']
+                for k,v in origpartorder.items():
+                    # Don't change an int of 0 to None. This allows it to be
+                    # caught in the regex later in case the single quotes were
+                    # forgotten in the config.
+                    if not v and v != 0 and v is not None:
+                        tmp[k] = None
+                    else:
+                        tmp[k] = v
+
+                if 'after' not in tmp:
+                    tmp['after'] = None
+                if 'before' not in tmp:
+                    tmp['before'] = None
+                self.partial_order_requirements.append(tmp)
+
+        self.__verify_match_requirements(self.match_requirements)
+
+        # Remove any duplicates from our list of CEL EventName's
+        self.cel_eventnames = list(set(self.cel_eventnames))
+
+    def __verify_match_requirements(self, match_requirements):
+        cel_cnt = 0
+
+        for item in range(0, len(match_requirements)):
+            # Fixup empty strings defined in the test's yaml file.
+            for k, v in match_requirements[item].items():
+                if not v:
+                    logger.debug("No expected header for '%s', using '^$'" % k)
+                    match_requirements[item][k] = '^$'
+            # Build list of CEL EventName's from test's yaml file. Used later on to
+            # ignore any received event where the EventName is not found in this list.
+            if "EventName" not in match_requirements[item]:
+                logger.error("No EventName specified. Aborting test.")
+                raise Exception
+            eventname = match_requirements[item].get('EventName')
+            if eventname is None:
+                logger.error("No value found for 'EventName' or it's not "
+                        "defined in the test's yaml file. Aborting test.")
+                raise Exception
+
+            self.cel_eventnames.append(eventname)
+
+            # Check if 'after' is found in the test's yaml file for this event
+            # requirement and if it's a string of one or more digits.
+            after = self.partial_order_requirements[item].get('after')
+            if after is not None:
+                try:
+                    if not re.match('^[a-zA-Z0-9_-]*$', after):
+                        logger.error("CEL ID '%s': The value of 'after' must "
+                                "be a string. Aborting test." %
+                                self.cel_ids[cel_cnt])
+                        raise Exception
+                except TypeError:
+                    logger.error("CEL ID '%s': The value of 'after' must be a "
+                            "string. Aborting test." %
+                            self.cel_ids[cel_cnt])
+                    raise Exception
+
+            # Check if 'before' is found in the test's yaml file for this event
+            # requirement and if it's a string of one or more digits.
+            before = self.partial_order_requirements[item].get('before')
+            if before is not None:
+                try:
+                    if not re.match('^[a-zA-Z0-9_-]*$', before):
+                        logger.error("CEL id '%s': The value of 'before' must "
+                                "be a string. Aborting test." %
+                                self.cel_ids[cel_cnt])
+                        raise Exception
+                except TypeError:
+                    logger.error("CEL ID '%s': The value of 'before' must be "
+                            "a string. Aborting test." %
+                            self.cel_ids[cel_cnt])
+                    raise Exception
+
+            # Get dict from list for whatever index we are on:
+            d = match_requirements[item]
+
+            # Convert the dict keys to lowercase but leave the values as is for our
+            # expected event.
+            tmp = [(k.lower(),v) for k,v in d.items()]
+            match_requirements.append(dict(tmp))
+            cel_cnt += 1
+
+        # Remove the orignal items leaving only those whose keys are now lower
+        # case
+        for x in range(0, cel_cnt):
+            match_requirements.pop(0)
+
+        return match_requirements
+
+    def event_callback(self, ami, event):
+        # Ignore any received CEL events where the EventName is not a
+        # requirement listed in the test's yaml.
+        eventname = event.get('eventname')
+        if eventname not in self.cel_eventnames:
+            logger.debug("Ignoring CEL EventName '%s'" % eventname)
+            return
+
+        # Check if we already made a match for this expected event. If so then
+        # skip it.
+        for m in range(self.match_index, len(self.match_requirements)):
+            if self.cel_ids[self.match_index] in self.matched_events:
+                logger.info("Skipping requirement ID '%s' since we already "
+                        "matched it." % self.cel_ids[self.match_index])
+                self.match_index += 1
+
+        logger.info("Expecting requirement ID '%s'" %
+                self.cel_ids[self.match_index])
+        # Get dict from list for whatever index we are on:
+        expected = self.match_requirements[self.match_index]
+
+        for k,v in expected.items():
+            if self.marked_failed: return
+            if k not in event.keys():
+                self.mark_failed('nokey', self.cel_ids[self.match_index], k, event)
+                return
+            if not re.match(v, event.get(k)):
+                match_found = False
+                submatch_found = False
+                logger.debug("A requirement 'match' was NOT met against "
+                        "requirement ID '%s'" % self.cel_ids[self.match_index])
+                # Check partial order to see if being out of order is allowed
+                self.check_partorder_exists(self.match_index, event,
+                        match_found)
+                if self.marked_failed: return
+                # See if this received event matches any other requirements.
+                submatch_found = self.find_requirement_match(event)
+                if self.marked_failed: return
+                # Now lets see if this requirement has a partial order specified
+                self.check_partorder_exists(self.submatch_index, event,
+                        match_found, submatch_found)
+            else:
+                match_found = True
+                logger.debug("A requirement 'match' was met against "
+                        "requirement ID '%s'" % self.cel_ids[self.match_index])
+                self.check_partorder_exists(self.match_index, event,
+                        match_found)
+
+        if self.submatch_index is not None:
+            logger.info("All criteria has been met for requirement ID '%s'" %
+                    self.cel_ids[self.submatch_index])
+            self.matched_events.append(self.cel_ids[self.submatch_index])
+            self.submatch_index = None
+            # Not incrementing self.match_index here since we're still
+            # expecting the requirement that we haven't successfully matched to
+        else:
+            logger.info("All criteria has been met for requirement ID '%s'" %
+                    self.cel_ids[self.match_index])
+            self.matched_events.append(self.cel_ids[self.match_index])
+            # Increment so we expect a new requirement since we matched the
+            # one we were on.
+            self.match_index += 1
+
+        logger.debug("Matched requirements so far: %s" % self.matched_events)
+        return (ami, event)
+
+    def check_partorder_exists(self, index, event, match_found,
+            submatch_found = None):
+        after = self.partial_order_requirements[index].get('after')
+        before = self.partial_order_requirements[index].get('before')
+        # If a before/after partial order isn't defined for this expected event
+        # then we must fail it as the expected event did not match the
+        # received event.
+        logger.debug("Checking if partial order exists on requirement ID "
+                "'%s'" % self.cel_ids[index])
+
+        if match_found:
+            # if no order specified then we want this to pass
+            if after is None and before is None:
+                return
+            else:
+                logger.debug("Partial order found on requirement ID '%s'. "
+                        "Strict ordering NOT enforced." %
+                        self.cel_ids[index])
+                # check before and after order
+                self.check_order(index, event, match_found)
+                return
+        if not match_found and not submatch_found:
+            if after is None and before is None:
+                logger.debug("No partial order found on requirement ID "
+                        "'%s'. Strict ordering enforced." %
+                        self.cel_ids[index])
+                self.mark_failed('partial_order', self.cel_ids[index], event)
+                return
+            else:
+                logger.debug("Found partial order on requirement ID '%s'. "
+                        "Strict ordering NOT enforce for this expected event." %
+                        self.cel_ids[index])
+                return
+        if not match_found and submatch_found:
+            if after is None and before is None:
+                logger.debug("No partial order found on requirement ID "
+                        "'%s'. Strict ordering enforced on this sub match." %
+                        self.cel_ids[index])
+                self.mark_failed('partial_order', self.cel_ids[index], event)
+                return
+            else:
+                logger.debug("Found partial order on requirement ID '%s'. "
+                        "Strict ordering NOT enforce for this expected event "
+                        "sub match." % self.cel_ids[index])
+                # check before and after order
+                self.check_order(index, event, match_found)
+                return
+
+    def find_requirement_match(self, event):
+        logger.debug("Trying to find a requirement that matches")
+        submatch_found = False
+        # Search from our current index+1(since we know it doesn't match our
+        # current index) to the last
+        for i in range(self.match_index + 1, len(self.match_requirements)):
+            # Get dict from list for whatever index we are on:
+            expected = self.match_requirements[i]
+
+            hdrmatchcnt = 0
+            numkeys = 0
+            for k,v in expected.items():
+                numkeys += 1
+                if re.match(v, event.get(k)):
+                    hdrmatchcnt += 1
+            if hdrmatchcnt == numkeys:
+                self.submatch_index = i
+                submatch_found = True
+                break
+
+        if submatch_found:
+            logger.debug("Found a sub requirement that matches at ID '%s'" %
+                    self.cel_ids[self.submatch_index])
+        else:
+            self.mark_failed('nomatches', None, None, event)
+
+        return submatch_found
+
+    def check_order(self, index, event, match_found):
+        # Check if the 'after' partial order requirement is met.
+        matched_after = self.check_after_order(index)
+
+        # Check if the 'before' partial order requirement is met.
+        matched_before = self.check_before_order(index)
+
+        if not matched_after or not matched_before:
+            if match_found:
+                logger.debug("A match was found at requirement ID '%s' "
+                        "but the partial order requirement failed!" %
+                        self.cel_ids[index])
+            else:
+                logger.debug("A match was found at sub requirement ID "
+                        "'%s' but the partial order requirement failed!" %
+                        self.cel_ids[index])
+                logger.warning("Received event does *NOT* match expected "
+                        "event")
+            self.mark_failed('partial_order', self.cel_ids[index])
+
+    def check_after_order(self, index):
+        # Lets see if we have a 'after' partial order specified for this
+        # expected event we found to match the received event.
+        logger.debug("Checking the 'after' partial order requirements for expected "
+                "event at requirement ID '%s'" % self.cel_ids[index])
+        # We know that either 'after' or 'before' has a value since
+        # check_partorder_exists() already told us that at least one of them does.
+        # If 'after' is None then it's NOT a problem and therefore we set our
+        # var to True.
+        if self.partial_order_requirements[index].get('after') is None:
+            after_range_matched = True
+        else:
+            after = self.partial_order_requirements[index].get('after')
+            # Check the 'after' order specified on the expected event that was
+            # matched to see if the expected event corresponding to the value of
+            # 'after' was already matched or not.
+            if after in self.matched_events:
+                logger.debug("The expected match for requirement ID '%s' did "
+                        "occur after the requirement ID '%s'" %
+                        (self.cel_ids[index], after))
+                after_range_matched = True
+            else:
+                logger.debug("The expected match for requirement ID '%s' did "
+                        "*NOT* occur after the requirement ID '%s'" %
+                        (self.cel_ids[index], after))
+                after_range_matched = False
+
+        return after_range_matched
+
+    def check_before_order(self, index):
+        logger.debug("Checking the 'before' partial order requirements for expected "
+                "event at requirement ID '%s'" % self.cel_ids[index])
+        # We know that either 'after' or 'before' has a value since
+        # check_partorder_exists() already told us that at least one of them does.
+        # If 'before' is None then it's NOT a problem and therefore we set our
+        # var to True.
+        if self.partial_order_requirements[index].get('before') is None:
+            before_range_matched = True
+        else:
+            before = self.partial_order_requirements[index].get('before')
+            # Check the 'before' order specified on the expected event that was
+            # matched to see if the expected event corresponding to the value of
+            # 'before' was already matched or not.
+            if before not in self.matched_events:
+                logger.debug("The expected match for requirement ID '%s' did "
+                        "occur before the requirement ID '%s'" %
+                        (self.cel_ids[index], before))
+                before_range_matched = True
+            else:
+                logger.debug("The expected match for requirement ID '%s' did "
+                        "*NOT* occur before the requirement ID '%s'" %
+                        (self.cel_ids[index], before))
+                before_range_matched = False
+
+        return before_range_matched
+
+    def mark_failed(self, item_failed, cel_id, expected = None, received = None):
+        self.passed = False
+        self.marked_failed = True
+        if item_failed == "partial_order":
+            logger.error("The partial order failed or doesn't exist for "
+                    "requirement ID '%s'" % cel_id)
+        if item_failed == "match":
+            logger.error("The match failed for requirement ID '%s'" % cel_id)
+            logger.error("=== Event expected ===")
+            logger.error(expected)
+            logger.error("=== Event received ===")
+            logger.error(received)
+        if item_failed == "nomatches":
+            logger.error("No requirement could be matched for the received "
+                    "event:")
+            logger.error("%s" % received)
+        if item_failed == "nokey":
+            logger.error("Required CEL key '%s' not found in received "
+                    "event" % expected)
+            logger.error("=== Event received ===")
+            logger.error(received)
+
+        logger.debug("Marking test as failed!")
+        return
+
+    def check_result(self, callback_param):
+        self.test_object.set_passed(self.passed)
+        return callback_param
+
 class AMICallbackInstance(AMIEventInstance):
     '''
     Subclass of AMIEventInstance that operates by calling a user-defined
@@ -267,6 +661,9 @@
         elif instance_type == "orderedheadermatch":
             logger.debug("instance type is 'orderedheadermatch'")
             return AMIOrderedHeaderMatchInstance(instance_config, test_object)
+        elif instance_type == "cel":
+            logger.debug("instance type is 'cel'")
+            return AMICel(instance_config, test_object)
         elif instance_type == "callback":
             logger.debug("instance type is 'callback'")
             return AMICallbackInstance(instance_config, test_object)

Modified: asterisk/trunk/sample-yaml/ami-config.yaml.sample
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/sample-yaml/ami-config.yaml.sample?view=diff&rev=3783&r1=3782&r2=3783
==============================================================================
--- asterisk/trunk/sample-yaml/ami-config.yaml.sample (original)
+++ asterisk/trunk/sample-yaml/ami-config.yaml.sample Fri May 17 15:53:14 2013
@@ -87,6 +87,143 @@
         # above.
         count: '2'
     -
+        # The "cel" type allows for checking CEL events via AMI.
+        # This is based on the "orderedheadermatch" type but allows for partial
+        # ordering of the events as they are not always generated in the order
+        # expected. A condition match of "Event: 'CEL'" is always used for this
+        # type and can not be overridden. Like the "orderedheadermatch" type,
+        # when the condition match is made, the first requirement in the list
+        # is expected. A requirement consists of 'match' criteria and
+        # optionally a 'partialorder'. Here is a break down:
+        #
+        # * If the event does successfully meet the 'match' critera of the
+        #   requirement expected and...
+        #    * a 'partialorder' is not specified, then this requirement
+        #      passes. Therefore the second requirement in the
+        #      requirements list is then expected.
+        #    * a 'partialorder' is specified and...
+        #       * the criteria is also met, then the requirement
+        #         passes. Therefore the second requirement in the
+        #         requirements list is then expected.
+        #       * the criteria is not met, the test is failed.
+        # * If it does not meet the 'match' criteria of the requirement
+        #   expected and...
+        #    * a partial order is not specified, the test is failed.
+        #    * a partial order is specified, it will remember the
+        #      requirement it is on and check if the 'match' criteria of
+        #      the next requirement in the list is met. It will continue
+        #      with each requirement in the order listed (where all the
+        #      criteria hasn't previously been met) until the 'match'
+        #      criteria is met or the end of the requirements list has
+        #      been reached at which point the test is failed.
+        #        * If the 'match' critera is successfully met and a...
+        #           * partial order is not specified, the test is
+        #             failed as the event arrived to early.
+        #           * partial order is specified and it's criteria
+        #             is not met, the test is failed.
+        #           * partial order is specified and it's criteria
+        #             is met, it is considered a successful match. It will
+        #             then move on and expect the next requirement in the
+        #             list from the point of the remembered requirement.
+        #
+        #  A simple example is shown below.
+
+        type: 'cel'
+
+        # Conditions are handled exactly the same as for "headermatch" types.
+        conditions:
+            match:
+                Channel: 'SIP/alice-.*|SIP/bob-.*'
+
+        # If an event is received with an EventName that is not in the list of
+        # requirements, it is ignored. A unique 'id' string must be set for
+        # each requirement. If a partial order is used then it would reference
+        # the id's of other requirements.
+        requirements:
+            -
+                # The 'match' criteria of this requirement must be met
+                # since a partial order is not specified. If it's not
+                # met then the test fails.
+                id: 'zero'
+                match:
+                    EventName: 'CHAN_START'
+                    Channel: 'SIP/alice-.*'
+            -
+                # This requirement is then expected and the 'match' criteria
+                # must be met as a partial order isn't specified here either.
+                # If it's not met then the test fails.
+                id: 'one'
+                match:
+                    EventName: 'CHAN_START'
+                    Channel: 'SIP/bob-.*'
+            -
+                # This requirement is then expected. If the 'match' criteria is
+                # not met it will check if the 'match' critia of the next
+                # requirement in the list is met since a 'partialorder' is
+                # specified. If the 'match' criteria is met but the
+                # 'partialorder' criteria is not met, the test is failed. If
+                # both the 'match' and 'partialorder' criteria is met, the next
+                # requirement in the list is expected.
+                id: 'two'
+                # Here we allow for a partial match. As an example, it may
+                # not be known if the HANGUP CEL event for SIP/alice-.*
+                # will be received before or after the HANGUP CEL event for
+                # SIP/bob-.*. If all that is cared about is that it's been
+                # received for both channels then a partial order should be
+                # specified. If the 'match' criteria is met then the
+                # 'after' and 'before' partial order criteria will be
+                # checked. If both partialorder criteria is met, then this
+                # requirement passes and the next requirement in the list
+                # is expected. If any of the partialorder criteria fails,
+                # then the test fails. If the 'match' criteria is not met
+                # it will check if the 'match' critia of the next
+                # requirement in the list is met since a 'partialorder' is
+                # specified.
+                partialorder:
+                    # Here we specify that this requirement must arrive
+                    # after ID 'zero' and before ID 'four'. If the
+                    # EventName is 'HANGUP' and the Channel is bob's
+                    # instead of alice's, the next requirement is checked
+                    # and all the criteria will be met there in this case.
+                    # This requirement will still be expected upon the next
+                    # CEL event. So if the HANGUP arrives for alice's
+                    # channel it will meet all the criteria for this
+                    # requirement.
+                    after: 'zero'
+                    before: 'four'
+                match:
+                    EventName: 'HANGUP'
+                    Channel: 'SIP/alice-.*'
+            -
+                # The same as the previous requirement but with a slightly
+                # different partialorder.
+                id: 'three'
+                partialorder:
+                    # Here we specify that this requirement must arrive
+                    # after ID 'one' and before ID 'four'. The criteria for
+                    # bob's channel will be met here.
+                    after: 'one'
+                    before: 'four'
+                match:
+                    EventName: 'HANGUP'
+                    Channel: 'SIP/bob-.*'
+            -
+                # This is the last requirement that must be met for the test to
+                # be able to pass.
+                id: 'four'
+                partialorder:
+                    # We specify that this requirement can only be met if
+                    # the requirement for ID 'three' has already been met
+                    # (of course the 'match' criteria for this requirement
+                    # must also have been met).
+                    after: 'three'
+                match:
+                    # We specify that either alice's or bob's channel can
+                    # match here as it will depend on the order in which
+                    # they were hung up.
+                    EventName: 'LINKEDID_END'
+                    Channel: 'SIP/alice-.*|SIP/bob-.*'
+    -
         # The "callback" type indicates that when event conditions are fulfilled
         # A callback should be called into. This is useful if pass/fail conditions
         # depend on more than just having specific headers match what is expected.




More information about the asterisk-commits mailing list