[Asterisk-code-review] pjsip/statsd/contacts: Sporadically failing due to unexpecte... (testsuite[master])

Jenkins2 asteriskteam at digium.com
Wed Jun 6 11:38:13 CDT 2018


Jenkins2 has submitted this change and it was merged. ( https://gerrit.asterisk.org/9050 )

Change subject: pjsip/statsd/contacts: Sporadically failing due to unexpected messages
......................................................................

pjsip/statsd/contacts: Sporadically failing due to unexpected messages

After the pjsip qualify rewrite the contacts statsd test failed fairly
regularly because it was now receiving two messages that it did not receive
before the rewrite. Namely, the messages that occur on shutdown. However,
due to some current shutdown handling in Asterisk these final messages still
may or may not be received by the test.

Unfortunately, the way the statsd tests were written did not allow for optional
messages. Really it only allowed for a strict result set that was not too
configurable.

This patch alleviates the brittleness, and fixes the test, by creating
suitable condition based message handling and matching routines that the test
can now use for optional messages received.

An attempt was made to generically write the message matching routines in hopes
that other future event listeners and handlers could take advantage of it.

Change-Id: Iaae769c7a4fe2dcac4865eb7dc4e5b6a1b25900b
---
A lib/python/asterisk/matcher.py
A lib/python/asterisk/matcher_listener.py
M lib/python/asterisk/pluggable_modules.py
A lib/python/asterisk/self_test/test2_matcher.py
D lib/python/mockd.py
M self_test
M tests/channels/pjsip/statsd/contacts/test-config.yaml
M tests/channels/pjsip/statsd/registrations/test-config.yaml
8 files changed, 775 insertions(+), 188 deletions(-)

Approvals:
  Joshua Colp: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved
  Jenkins2: Approved for Submit



diff --git a/lib/python/asterisk/matcher.py b/lib/python/asterisk/matcher.py
new file mode 100644
index 0000000..87c62be
--- /dev/null
+++ b/lib/python/asterisk/matcher.py
@@ -0,0 +1,327 @@
+"""Module for handling pattern and conditional message matching and
+aggregation.
+
+Copyright (C) 2018, Digium, Inc.
+Kevin Harwell <kharwell at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+
+import logging
+
+from test_suite_utils import all_match
+from pluggable_registry import PLUGGABLE_EVENT_REGISTRY,\
+    PLUGGABLE_ACTION_REGISTRY
+
+
+LOGGER = logging.getLogger(__name__)
+
+
+class ConditionError(Exception):
+    """Error raised a condition(s) fail"""
+
+    def __init__(self, failures):
+        """Create a condition(s) error exception.
+
+        Keyword Arguments:
+        failures - A list of failed condition objects
+        """
+
+        if not isinstance(failures, list):
+            failures = [failures]
+
+        msg = ''
+        for f in failures:
+            msg += f.error() + '\n'
+
+        super(ConditionError, self).__init__(msg)
+
+
+class Condition(object):
+
+    @staticmethod
+    def create(config, match_fun=None):
+        """Create a condition object from the given configuration.
+
+        Keyword Arguments:
+        config - Data in a dictionary format used in object creation
+        match_fun - The function used to match the pattern and value
+
+        Configuration options:
+        match - A regex string, dictionary, or list.
+        count - Expected number of matches. Can be a single value (ex: 2),
+            range (ex: 2-5), or a lower (ex: <4) or upper (ex: >2) limit.
+        optional - alias for 'match' and expects 0 or 1 matches.
+        """
+
+        if isinstance(config, str) or isinstance(config, unicode):
+            config = {'match': config}
+
+        if 'optional' in config:
+            config['match'] = config['optional']
+            minimum = '<2'
+        else:
+            minimum, _, maximum = str(config.get('count', '1')).partition('-')
+
+        if minimum.startswith('>'):
+            minimum = int(minimum[1:]) + 1
+            maximum = float('inf')
+        elif minimum.startswith('<'):
+            maximum = int(minimum[1:]) - 1
+            minimum = 0
+        else:
+            minimum = int(minimum)
+            maximum = int(maximum) if maximum else minimum
+
+        if minimum > maximum:
+            raise SyntaxError("Invalid count: minimum '{0}' can't be greater "
+                              "than maximum '{1}'".format(minimum, maximum))
+
+        return Condition(config.get('match'), minimum, maximum)
+
+    def __init__(self, pattern=None, minimum=1, maximum=1, match_fun=None):
+        """Constructor
+
+        Keyword Arguments:
+        pattern - The pattern that will be checked against
+        minimum - The expected minimum number of matches
+        maximum - The expected maximum number of matches
+        match_fun - The function used to match the pattern and value
+        """
+
+        self.pattern = pattern
+        self.minimum = minimum
+        self.maximum = maximum
+        self.match_fun = match_fun or all_match
+
+        self.count = 0
+
+    def check_match(self, value):
+        """Check if the given value matches the expected pattern.
+
+        Keyword Arguments:
+        value - The value to check against the expected pattern
+        """
+
+        if self.match_fun(self.pattern, value):
+            LOGGER.debug("Matched condition: {0}".format(self.pattern))
+            self.count += 1
+            return True
+
+        return False
+
+    def check_max(self):
+        """Check if the current match count is less than or equal to the
+        configured maximum.
+        """
+
+        return self.count <= self.maximum
+
+    def check_min(self):
+        """Check if the current match count is greater than or equal to the
+        configured minimum.
+        """
+
+        return self.count >= self.minimum
+
+    def error(self):
+        """Error out the conditional."""
+
+        return ("\nCondition: '{0}'\nExpected >= {1} and <= {2} but "
+                "received {3}".format(self.pattern, self.minimum,
+                                      self.maximum, self.count))
+
+
+class Conditions(object):
+
+    @staticmethod
+    def create(config, on_match=None, match_fun=None):
+        """Create a conditions object from the given configuration.
+
+        Keyword Arguments:
+        config - Data in a dictionary format used in object creation
+        on_match - Optional callback to raise on a match. Handler Must be
+            proto-typed as 'handler(matched, value)'
+        match_fun - Optional function used to match the pattern and value
+
+        Configuration options:
+        conditions - A list of condition configuration data
+        trigger-on-any - Raise the 'on_match' event if any condition has been
+            fully met (meaning at least one match with all its minimum met).
+            Defaults to False
+        trigger-on-all - Raise the 'on_match' event if all conditions have been
+            fully met (meaning all have matched and met their minimum).
+            Defaults to True
+
+        Note:
+        If both trigger-on-any and trigger-on-all are False then the 'on_match'
+        event is raised upon the first basic match (meaning a minimum may or
+        may not have been met yet)
+        """
+
+        conditions = []
+        for c in config['conditions']:
+            conditions.append(Condition.create(c, match_fun))
+
+        # Any is checked prior to all, so okay for all to also be True
+        return Conditions(conditions, config.get('trigger-on-any', False),
+                          config.get('trigger-on-all', True), on_match)
+
+    def __init__(self, conditions, trigger_on_any=False, trigger_on_all=True,
+                 on_match=None):
+        """Constructor
+
+        Keyword Arguments:
+        conditions - A list of condition objects
+        trigger_on_any - check returns true if any condition is met
+        trigger_on_all - check returns true if all conditions are met
+        on_match - Optional callback to raise on a match. Handler Must be
+            proto-typed as 'handler(matched, value)'
+        """
+
+        self.conditions = conditions or []
+        self.trigger_on_any = trigger_on_any
+        self.trigger_on_all = trigger_on_all
+        self.on_match = on_match or (lambda y, z: None)
+
+    def check(self, value):
+        """Check if the given value matches a stored pattern, and if so then
+        also make sure that any other relevant conditional criteria have been
+        met.
+
+        Keyword Arguments:
+        value - The value to check against the patterns
+
+        Return:
+        True given the following, false otherwise:
+            trigger_on_any was set and at least one required conditional
+            was met.
+
+            trigger_on_all was set and all required conditional were met.
+
+            An item matched, and neither of the above parameters were set.
+        """
+
+        matched = []
+        for c in self.conditions:
+            if not c.check_match(value):
+                continue
+
+            if not c.check_max():
+                raise ConditionError(c)
+
+            matched.append(c)
+
+
+        if not matched:
+            return False
+
+        if self.trigger_on_any:
+            if not any(c.check_min() for c in matched):
+                return False
+        elif self.trigger_on_all:
+            if not all(c.check_min() for c in self.conditions):
+                return False
+
+        LOGGER.debug("Conditions triggered: {0}".format([c.pattern for c in matched]))
+        self.on_match(matched, value)
+        return True
+
+    def check_final(self):
+        """Check final conditionals and fail on those not met."""
+
+        failures = [c for c in self.conditions if not c.check_min()]
+        if failures:
+            raise ConditionError(failures)
+        return True
+
+
+class PluggableConditions(object):
+
+    def __init__(self, config, test_object, on_match=None):
+        """Constructor
+
+        Keyword Arguments:
+        config - Configuration for this module
+        test_object - The test case driver
+        on_match - Optional callback to raise on a match. Handler Must be
+            proto-typed as 'handler(matched, value)'
+        """
+
+        self.config = config
+        self.test_object = test_object
+        self.test_object.register_stop_observer(self.__handle_stop)
+
+        self.conditions = Conditions.create(self.config, on_match)
+
+    def fail_and_stop(self, error_msg):
+        """Fail the test and stop the reactor.
+
+        Keyword Arguments:
+        error_msg - The error message to log
+        """
+
+        LOGGER.error(error_msg)
+
+        self.test_object.set_passed(False)
+        self.test_object.stop_reactor()
+
+    def check(self, value):
+        try:
+            return self.conditions.check(value)
+        except ConditionError as e:
+            self.fail_and_stop(e)
+
+    def check_final(self):
+        if self.test_object.passed:
+            try:
+                self.conditions.check_final()
+            except ConditionError as e:
+                self.fail_and_stop(e)
+        return self.test_object.passed
+
+    def __handle_stop(self, *args):
+        """Check any final conditions prior to test end.
+
+        Keyword Arguments:
+        args: Unused
+        """
+
+        self.check_final()
+
+
+class PluggableConditionsEventModule(object):
+    """Registry wrapper for pluggable conditional event checks."""
+
+    def __init__(self, test_object, triggered_callback, config):
+        """Constructor
+
+        Keyword Arguments:
+        test_object - The TestCase driver
+        triggered_callback - Conditionally called when matched
+        config - Configuration for this module
+
+        Configuration options:
+        type - The <module.class> of the object type to create that is
+            listens for events and passes event data to the conditional
+            matcher.
+        """
+
+        self.triggered_callback = triggered_callback
+
+        module_name, _, obj_type = config['type'].partition('.')
+
+        module = __import__(module_name, fromlist=[obj_type])
+        if not module:
+            raise Exception("Unable to import module '{0}'.".format(module_name))
+
+        obj = getattr(module, obj_type)
+
+        self.conditions = obj(config, test_object, self.__handle_match)
+
+    def __handle_match(self, matched, event):
+        self.triggered_callback(self, matched, event)
+
+
+PLUGGABLE_EVENT_REGISTRY.register('event', PluggableConditionsEventModule)
diff --git a/lib/python/asterisk/matcher_listener.py b/lib/python/asterisk/matcher_listener.py
new file mode 100644
index 0000000..d6b621d
--- /dev/null
+++ b/lib/python/asterisk/matcher_listener.py
@@ -0,0 +1,76 @@
+"""Module that match messages and events for a defined listener.
+
+Copyright (C) 2018, Digium, Inc.
+Kevin Harwell <kharwell at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+
+import logging
+import re
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+from matcher import PluggableConditions
+
+LOGGER = logging.getLogger(__name__)
+
+
+class UdpProtocol(DatagramProtocol):
+    """Protocol to use for receiving messages."""
+
+    def __init__(self, server):
+        """Constructor.
+
+        Keyword Arguments:
+        server - The udp server object
+        config - Configuration used by the protocol
+        """
+
+        self.server = server
+
+    def datagramReceived(self, datagram, address):
+        """Receive incoming data.
+
+        Keyword Arguments:
+        datagram - The data that was received
+        address - The address that the data came from
+        """
+
+        LOGGER.debug('Received %s from %s', datagram, address)
+        self.server.handle_message(datagram)
+
+
+class Udp(PluggableConditions):
+    """Pluggable module that that checks messages received over UDP"""
+
+    def __init__(self, config, test_object, on_match=None):
+        """Constructor
+
+        Keyword Arguments:
+        config - configuration for this module
+        test_object - the TestCase driver
+        on_match - Optional callback called upon a conditional match
+        """
+
+        super(Udp, self).__init__(config, test_object, on_match)
+
+        self.filter_msgs = config.get('filter', ['stasis.message', 'channels.'])
+
+        if not isinstance(self.filter_msgs, list):
+            self.filter_msgs = [self.filter_msgs]
+
+        reactor.listenUDP(config.get('port', 8125), UdpProtocol(self))
+
+    def handle_message(self, msg):
+        """Handle messages received over udp and check the message against the
+        configured conditions.
+
+        Keyword Arguments:
+        msg -- The message received via the udp
+        """
+
+        if not any(f for f in self.filter_msgs if re.match(f, msg)):
+            self.check(msg)
diff --git a/lib/python/asterisk/pluggable_modules.py b/lib/python/asterisk/pluggable_modules.py
index 256d222..344f908 100755
--- a/lib/python/asterisk/pluggable_modules.py
+++ b/lib/python/asterisk/pluggable_modules.py
@@ -22,6 +22,8 @@
     PLUGGABLE_EVENT_REGISTRY,\
     PluggableRegistry
 
+import matcher
+
 LOGGER = logging.getLogger(__name__)
 
 
diff --git a/lib/python/asterisk/self_test/test2_matcher.py b/lib/python/asterisk/self_test/test2_matcher.py
new file mode 100755
index 0000000..0357ce9
--- /dev/null
+++ b/lib/python/asterisk/self_test/test2_matcher.py
@@ -0,0 +1,299 @@
+#!/usr/bin/env python
+"""Module for testing the message_match module.
+
+Copyright (C) 2018, Digium, Inc.
+Kevin Harwell <kharwell at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+
+import logging
+import sys
+import unittest
+
+sys.path.append('lib/python')  # noqa
+from asterisk.matcher import PluggableConditions
+
+
+LOGGER = logging.getLogger(__name__)
+
+
+class TestObject(object):
+    """Simple test object that implements methods used by message
+    match conditions.
+    """
+
+    def __init__(self):
+        """Constructor"""
+
+        self.passed = True
+
+    def set_passed(self, passed):
+        """Set the passed value"""
+
+        self.passed = passed
+
+    def stop_reactor(self):
+        """Noop"""
+
+        pass
+
+    def register_stop_observer(self, callback):
+        """Noop"""
+
+        pass
+
+
+class PluggableConditionsTests(unittest.TestCase):
+    """Unit tests for message match conditions."""
+
+    def setUp(self):
+        """Setup test object"""
+
+        self.test_object = TestObject()
+
+    def test_001_defaults(self):
+        """Test condition defaults"""
+
+        config = {'conditions': ['hello']}
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+    def test_002_match(self):
+        """Test basic matching"""
+
+        config = {
+            'conditions': [
+                {'match': 'hello'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+    def test_003_optional(self):
+        """Test optional matching"""
+
+        config = {
+            'conditions': [
+                {'optional': 'hello'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        # Check with no message handled
+        self.assertTrue(conditions.check_final())
+
+        # Now check once message handled
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+    def test_004_count(self):
+        """Test count condition"""
+
+        config = {
+            'conditions': [
+                {'match': 'hello', 'count': '2'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        # Check with no message handled
+        self.assertFalse(conditions.check_final())
+
+        # Check with one message handled
+        self.test_object.set_passed(True)
+        self.assertFalse(conditions.check('hello'))
+        self.assertFalse(conditions.check_final())
+
+        # Check with two messages handled
+        self.test_object.set_passed(True)
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+        # Check with three messages handled
+        self.assertFalse(conditions.check('hello'))
+        self.assertFalse(conditions.check_final())
+
+    def test_005_count(self):
+        """Test range count condition"""
+
+        config = {
+            'conditions': [
+                {'match': 'hello', 'count': '2-4'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        # Check with no message handled
+        self.assertFalse(conditions.check_final())
+
+        # Check with one message handled
+        self.test_object.set_passed(True)
+        self.assertFalse(conditions.check('hello'))
+        self.assertFalse(conditions.check_final())
+
+        # Check with two messages handled
+        self.test_object.set_passed(True)
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+        # Check with four messages handled
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+        # Check with five messages handled
+        self.assertFalse(conditions.check('hello'))
+        self.assertFalse(conditions.check_final())
+
+    def test_006_min(self):
+        """Test minimum condition"""
+
+        config = {
+            'conditions': [
+                {'match': 'hello', 'count': '>1'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        # Check with no message handled
+        self.assertFalse(conditions.check_final())
+
+        # Check with one message handled
+        self.test_object.set_passed(True)
+        self.assertFalse(conditions.check('hello'))
+        self.assertFalse(conditions.check_final())
+
+        # Check with two messages handled
+        self.test_object.set_passed(True)
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+    def test_007_max(self):
+        """Test maximum condition"""
+
+        config = {
+            'conditions': [
+                {'match': 'hello', 'count': '<2'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        # Check with no message handled
+        self.assertTrue(conditions.check_final())
+
+        # Check with one message handled
+        self.assertTrue(conditions.check('hello'))
+        self.assertTrue(conditions.check_final())
+
+        # Check with two messages handled
+        self.assertFalse(conditions.check('hello'))
+        self.assertFalse(conditions.check_final())
+
+    def test_008_max(self):
+        """Test no match condition"""
+
+        config = {
+            'conditions': [
+                {'match': 'hello', 'count': '0'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        # Check with no message handled
+        self.assertTrue(conditions.check_final())
+
+        # Check with one message handled
+        self.assertFalse(conditions.check('hello'))
+        self.assertFalse(conditions.check_final())
+
+    def test_009_trigger_on_any(self):
+        """Test trigger on any option"""
+
+        config = {
+            'trigger-on-any': True,
+            'conditions': [
+                {'match': 'hello', 'count': '1'},
+                {'match': 'world', 'count': '2'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        self.assertFalse(conditions.check('world'))
+        self.assertFalse(conditions.check_final())
+
+        self.test_object.set_passed(True)
+        self.assertTrue(conditions.check('hello'))  # 'hello' is met
+        self.assertFalse(conditions.check_final())  # 'world' not me
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        self.assertFalse(conditions.check('world'))
+        self.assertFalse(conditions.check_final())
+
+        self.test_object.set_passed(True)
+        self.assertTrue(conditions.check('world'))  # 'world' is met
+        self.assertFalse(conditions.check_final())  # 'hello' not met
+
+    def test_010_trigger_on_all(self):
+        """Test trigger on all option"""
+
+        config = {
+            'trigger-on-all': True,
+            'conditions': [
+                {'match': 'hello', 'count': '1'},
+                {'match': 'world', 'count': '2'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        self.assertFalse(conditions.check('world'))
+        self.assertFalse(conditions.check_final())
+
+        self.test_object.set_passed(True)
+        self.assertFalse(conditions.check('hello'))  # 'world' not met
+        self.assertFalse(conditions.check_final())
+
+        self.test_object.set_passed(True)
+        self.assertTrue(conditions.check('world'))
+        self.assertTrue(conditions.check_final())
+
+    def test_010_trigger_on_first(self):
+        """Test trigger on first match (other conditions don't apply)"""
+
+        config = {
+            'trigger-on-any': False,
+            'trigger-on-all': False,
+            'conditions': [
+                {'match': 'hello', 'count': '1'},
+                {'match': 'world', 'count': '2'}
+            ]
+        }
+
+        conditions = PluggableConditions(config, self.test_object)
+
+        self.assertTrue(conditions.check('world'))
+        self.assertFalse(conditions.check_final())  # 'hello' & 'world' not met
+
+
+if __name__ == "__main__":
+    """Run the unit tests"""
+
+    logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
+                        format="%(module)s:%(lineno)d - %(message)s")
+    unittest.main()
diff --git a/lib/python/mockd.py b/lib/python/mockd.py
deleted file mode 100644
index 3d4299b..0000000
--- a/lib/python/mockd.py
+++ /dev/null
@@ -1,114 +0,0 @@
-'''
-Copyright (C) 2015, Digium, Inc.
-Tyler Cambron <tcambron at digium.com>
-
-This program is free software, distributed under the terms of
-the GNU General Public License Version 2.
-'''
-
-import sys
-import logging
-
-from twisted.internet.protocol import DatagramProtocol
-from twisted.internet import reactor
-from test_suite_utils import all_match
-
-LOGGER = logging.getLogger(__name__)
-
-
-class MockDProtocol(DatagramProtocol):
-    '''Protocol for the Mock Server to use for receiving messages.'''
-
-    def __init__(self, mockd_server):
-        '''Constructor.
-
-        Keyword Arguments:
-        mockd_server -- An instance of the mock StatsD server
-        '''
-        self.mockd_server = mockd_server
-
-    def datagramReceived(self, datagram, address):
-        '''An override function to handle incoming datagrams.
-
-        Keyword Arguments:
-        datagram -- The datagram that was received by the server
-        address -- The address that the datagram came from
-
-        Accept the datagram and send it to be checked against the config
-        '''
-        skip = ['stasis.message', 'channels.']
-        LOGGER.debug('Server received %s from %s', datagram, address)
-
-        if not (skip[0] in datagram or skip[1] in datagram):
-            self.mockd_server.message_handler(datagram)
-
-
-class MockDServer(object):
-    '''Pluggable Module that acts as a mock StatsD server'''
-
-    def __init__(self, config, test_object):
-        '''Constructor
-
-        Keyword Arguments:
-        config -- This object's YAML derived configuration
-        test_object -- The test object it plugs onto
-        '''
-        self.config = config
-        self.test_object = test_object
-        self.packets = []
-        self.prefix = self.config.get('prefix')
-
-        self.test_object.register_stop_observer(self._stop_handler)
-
-        reactor.listenUDP(8125, MockDProtocol(self))
-
-    def message_handler(self, message):
-        '''Datagram message handler
-
-        Keyword Arguments:
-        message -- The datagram that was received by the server
-
-        Check the message against the config and pass the test if they match
-        '''
-        if self.prefix and not message.startswith(self.prefix):
-            return
-        self.packets.append(message)
-
-    def _stop_handler(self, result):
-        '''A deferred callback called as a result of the test stopping
-
-        Keyword Arguments:
-        result -- The deferred parameter passed from callback to callback
-        '''
-        LOGGER.info('Checking packets received')
-
-        packets = self.config.get('packets')
-
-        if (packets[0] == 'ReceiveNothing') and (len(self.packets) == 0):
-            LOGGER.info('Server correctly received nothing')
-            self.test_object.set_passed(True)
-            return result
-
-        if len(self.packets) != len(packets):
-            LOGGER.error('Number of received packets {0} is not equal to '
-                'the number of configured packets '
-                '{1}'.format(len(self.packets), len(packets)))
-            self.test_object.set_passed(False)
-            return result
-
-        if self.config.get('regex', False):
-            cmp_fn = all_match
-        else:
-            cmp_fn = lambda expected, actual: expected == actual
-        failed_matches = [(actual, expected) for actual, expected in
-            zip(self.packets, packets) if not cmp_fn(expected, actual)]
-
-        if len(failed_matches) != 0:
-            LOGGER.error('The following packets failed to match: {0}'
-                .format(failed_matches))
-            self.test_object.set_passed(False)
-            return result
-
-        self.test_object.set_passed(True)
-        LOGGER.info('All packets matched')
-        return result
diff --git a/self_test b/self_test
index 02d8e21..1936014 100755
--- a/self_test
+++ b/self_test
@@ -22,11 +22,11 @@
 	fi
 }
 
-ALL_TESTS=$(find lib/python/asterisk/self_test -name 'test_*.py' -exec basename '{}' .py \;)
+ALL_TESTS=$(find lib/python/asterisk/self_test -name 'test*.py' -exec basename '{}' .py \;)
 for i in $ALL_TESTS; do
 	run_test $i python $PYTHON
 	run_test $i python2 $PYTHON2
-	run_test $i python3 $PYTHON3
+	[ "${i#test2}" = "${i}" ] && run_test $i python3 $PYTHON3
 done
 
 # Temporary code for running unit tests that are not compatible with python3
diff --git a/tests/channels/pjsip/statsd/contacts/test-config.yaml b/tests/channels/pjsip/statsd/contacts/test-config.yaml
index 5d4c66e..d4141a0 100644
--- a/tests/channels/pjsip/statsd/contacts/test-config.yaml
+++ b/tests/channels/pjsip/statsd/contacts/test-config.yaml
@@ -12,48 +12,49 @@
         typename: 'sipp.SIPpTestCase'
     modules:
         -
-            typename: 'mockd.MockDServer'
-            config-section: 'statsd-config'
+            config-section: event-action-config
+            typename: 'pluggable_modules.EventActionModule'
 
 test-object-config:
-    fail-on-any: False
     reactor-timeout: 10
     test-iterations:
         -
             scenarios:
                 - { 'key-args': {'scenario': 'options.xml', '-i': '127.0.0.1', '-p': '5061'} }
 
-statsd-config:
-    regex: True
-    prefix: 'PJSIP.contacts'
-    packets:
-        -
-            'PJSIP\.contacts\.states\.Unreachable:0\|g'
-        -
-            'PJSIP\.contacts\.states\.Reachable:0\|g'
-        -
-            'PJSIP\.contacts\.states\.Unknown:0\|g'
-        -
-            'PJSIP\.contacts\.states\.(Created|NonQualified):0\|g'
-        -
-            'PJSIP\.contacts\.states\.Removed:0\|g'
-        -
-            'PJSIP\.contacts\.states\.(Created|NonQualified):\+1\|g'
-        -
-            'PJSIP\.contacts\.states\.(Created|NonQualified):\-1\|g'
-        -
-            'PJSIP\.contacts\.states\.Reachable:\+1\|g'
-        -
-            'PJSIP\.contacts\.sipp@@d0c8ec670653c9643ca96622ef658bbb\.rtt:.*\|ms'
+event-action-config:
+    event:
+        type: 'matcher_listener.Udp'
+        conditions:
+            -
+                'PJSIP\.contacts\.states\.Unreachable:0\|g'
+            -
+                'PJSIP\.contacts\.states\.Reachable:0\|g'
+            -
+                'PJSIP\.contacts\.states\.Unknown:0\|g'
+            -
+                'PJSIP\.contacts\.states\.(Created|NonQualified):0\|g'
+            -
+                'PJSIP\.contacts\.states\.Removed:0\|g'
+            -
+                'PJSIP\.contacts\.states\.(Created|NonQualified):\+1\|g'
+            -
+                'PJSIP\.contacts\.states\.(Created|NonQualified):\-1\|g'
+            -
+                'PJSIP\.contacts\.states\.Reachable:\+1\|g'
+            -
+                'PJSIP\.contacts\.sipp@@d0c8ec670653c9643ca96622ef658bbb\.rtt:.*\|ms'
+            -
+                optional: 'PJSIP\.contacts\.states\.Reachable:\-1\|g'
+            -
+                optional: 'PJSIP\.contacts\.states\.Removed:\+1\|g'
 
 properties:
     dependencies:
-        - python: 'autobahn.websocket'
-        - python: 'starpy'
         - python: 'twisted'
-        - asterisk: 'res_pjsip'
         - asterisk: 'res_pjsip_outbound_registration'
         - asterisk: 'res_statsd'
+        - asterisk: 'res_pjsip'
     tags:
         - statsd
-        - apps
+        - pjsip
diff --git a/tests/channels/pjsip/statsd/registrations/test-config.yaml b/tests/channels/pjsip/statsd/registrations/test-config.yaml
index 4e69e6c..b27e092 100644
--- a/tests/channels/pjsip/statsd/registrations/test-config.yaml
+++ b/tests/channels/pjsip/statsd/registrations/test-config.yaml
@@ -4,61 +4,57 @@
         'This test performs an outbound registration, and verifies that
         the expected StatsD statistics are generated as a result.'
 
-properties:
-    dependencies:
-        - python: 'twisted'
-        - python: 'starpy'
-        - asterisk: 'res_pjsip'
-        - asterisk: 'res_pjsip_outbound_registration'
-        - asterisk: 'res_statsd'
-        - sipp:
-            version: 'v3.0'
-    tags:
-        - pjsip
-
 test-modules:
-    add-test-to-search-path: 'True'
     test-object:
         config-section: test-object-config
         typename: 'sipp.SIPpTestCase'
     modules:
         -
-            typename: 'mockd.MockDServer'
-            config-section: 'statsd-config'
+            config-section: event-action-config
+            typename: 'pluggable_modules.EventActionModule'
 
-statsd-config:
-    prefix: 'PJSIP.registrations'
-    packets:
-        -
-            'PJSIP.registrations.count:0|g'
-        -
-            'PJSIP.registrations.state.Registered:0|g'
-        -
-            'PJSIP.registrations.state.Unregistered:0|g'
-        -
-            'PJSIP.registrations.state.Rejected:0|g'
-        -
-            'PJSIP.registrations.count:+1|g'
-        -
-            'PJSIP.registrations.state.Unregistered:+1|g'
-        -
-            'PJSIP.registrations.state.Unregistered:-1|g'
-        -
-            'PJSIP.registrations.state.Registered:+1|g'
-        -
-            'PJSIP.registrations.state.Registered:-1|g'
-        -
-            'PJSIP.registrations.state.Unregistered:+1|g'
-        -
-            'PJSIP.registrations.count:-1|g'
-        -
-            'PJSIP.registrations.state.Unregistered:-1|g'
 
 test-object-config:
-    fail-on-any: False
     test-iterations:
         -
             scenarios:
                 - { 'key-args': {'scenario': 'register.xml', '-i': '127.0.0.1', '-p': '5061'} }
 
+event-action-config:
+    event:
+        type: 'matcher_listener.Udp'
+        conditions:
+            -
+                'PJSIP\.registrations\.count:0\|g'
+            -
+                'PJSIP\.registrations\.state\.Registered:0\|g'
+            -
+                'PJSIP\.registrations\.state\.Unregistered:0\|g'
+            -
+                'PJSIP\.registrations\.state\.Rejected:0\|g'
+            -
+                'PJSIP\.registrations\.count:\+1\|g'
+            -
+                'PJSIP\.registrations\.state\.Registered:\+1\|g'
+            -
+                'PJSIP\.registrations\.state\.Registered:\-1\|g'
+            -
+                match: 'PJSIP\.registrations\.state\.Unregistered:\+1\|g'
+                count: 2
+            -
+                'PJSIP\.registrations\.count:\-1\|g'
+            -
+                match: 'PJSIP\.registrations\.state\.Unregistered:\-1\|g'
+                count: 2
 
+properties:
+    dependencies:
+        - python: 'twisted'
+        - asterisk: 'res_statsd'
+        - asterisk: 'res_pjsip'
+        - asterisk: 'res_pjsip_outbound_registration'
+        - sipp:
+            version: 'v3.0'
+    tags:
+        - statsd
+        - pjsip

-- 
To view, visit https://gerrit.asterisk.org/9050
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-Project: testsuite
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Iaae769c7a4fe2dcac4865eb7dc4e5b6a1b25900b
Gerrit-Change-Number: 9050
Gerrit-PatchSet: 3
Gerrit-Owner: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Jenkins2
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20180606/24b28d2c/attachment-0001.html>


More information about the asterisk-code-review mailing list