[Asterisk-code-review] apptest: Add the ability to specify the channel object from ... (testsuite[master])

Matt Jordan asteriskteam at digium.com
Sat Sep 3 15:59:50 CDT 2016


Matt Jordan has uploaded a new change for review.

  https://gerrit.asterisk.org/3811

Change subject: apptest: Add the ability to specify the channel object from the dialplan
......................................................................

apptest: Add the ability to specify the channel object from the dialplan

AppTest assumes we have a Local channel pair entering into an
application that we want to control. One half of that Local channel pair
is used by AppTest to send DTMF, stream media, etc. - the other half is
the part that sits in the application under test. In order for AppTest
to know which half of the Local channel is which, it looks at the name
of the Local channel (exten at context) as well as NewExten events to
determine which half has gone into the application under test. This
presents two problems:
(1) Once a Local channel at some exten at context has been 'identified', a
    subsequent Local channel at the same dialplan location will cause
    AppTest to get confused. In the past, different extensions were used
    to control multiple channel pairs; however, there instances where
    the same exten at context has to be used for some tests.
(2) This scheme only works for inbound channels, and cannot handle
    outbound channels. In an outbound channel, the part of a Local
    channel in the application under test is reversed from the inbound
    channel scenario; in fact, we won't get a NewExten event for it at
    all.

This patch solves both of these problems. First, we can now mark a
channel object as being ignored until started. This prevents that
channel object, along with its associated actions, from executing just
because a Local channel exists that would normally match its
'channel-name' field. Once the channel object is started - either
through an action or through the 'start-on-create' property - its event
matchers will be used and considered for action.

Second, we can now identify a Local channel pair from the dialplan.
AppTest will monitor VarSet events and look for one that specifies a
'testuniqueid'. If it matches a channel object's 'testuniqueid' field,
it will associate that Local channel pair with that channel object. This
is completely independent of the application that is under test, and
allows to control both outbound channels as well as other ancillary
channels that might be needed in a complex test scenario.

ASTERISK-25691

Change-Id: I6a2f3da0d0d98f1c3230797b89275a43369e7d8b

Fixup

Change-Id: Ia114421b4a30fe39e77a4ed84be4e04cb8f7e8ef
---
M lib/python/asterisk/apptest.py
M sample-yaml/apptest-config.yaml.sample
2 files changed, 139 insertions(+), 57 deletions(-)


  git pull ssh://gerrit.asterisk.org:29418/testsuite refs/changes/11/3811/1

diff --git a/lib/python/asterisk/apptest.py b/lib/python/asterisk/apptest.py
index 64b592d..cc17b97 100644
--- a/lib/python/asterisk/apptest.py
+++ b/lib/python/asterisk/apptest.py
@@ -241,19 +241,21 @@
         self._channel_id = channel_def['channel-id']
         self._channel_name = channel_def['channel-name']
         self._applications = applications
-        self._controller_context = channel_def.get('context') \
-            or ChannelObject.default_context
-        self._controller_initial_exten = channel_def.get('exten') \
-            or ChannelObject.default_wait_exten
-        self._controller_hangup_exten = channel_def.get('hangup-exten') \
-            or ChannelObject.default_hangup_exten
-        self._controller_audio_exten = channel_def.get('audio-exten') \
-            or ChannelObject.default_audio_exten
-        self._controller_dtmf_exten = channel_def.get('dtmf-exten') \
-            or ChannelObject.default_dtmf_exten
-        self._controller_wait_exten = channel_def.get('wait-exten') \
-            or ChannelObject.default_wait_exten
-        delay = channel_def.get('delay') or 0
+        self._controller_context = channel_def.get('context',
+            ChannelObject.default_context)
+        self._controller_initial_exten = channel_def.get('exten',
+            ChannelObject.default_wait_exten)
+        self._controller_hangup_exten = channel_def.get('hangup-exten',
+            ChannelObject.default_hangup_exten)
+        self._controller_audio_exten = channel_def.get('audio-exten',
+            ChannelObject.default_audio_exten)
+        self._controller_dtmf_exten = channel_def.get('dtmf-exten',
+            ChannelObject.default_dtmf_exten)
+        self._controller_wait_exten = channel_def.get('wait-exten',
+            ChannelObject.default_wait_exten)
+        self._ignore_until_started = channel_def.get('ignore-until-started',
+            False)
+        delay = channel_def.get('delay', 0)
 
         self.ami = ami
         self.ami.registerEvent('Hangup', self._hangup_event_handler)
@@ -271,9 +273,21 @@
         self._test_observers = []
         self._hangup_observers = []
         self._candidate_prefix = ''
-        self._unique_id = str(uuid.uuid1())
+        self._started = False
+        self._unique_id = channel_def.get('testuniqueid', str(uuid.uuid1()))
         if 'start-on-create' in channel_def and channel_def['start-on-create']:
             self.spawn_call(delay)
+
+    def is_active(self):
+        """Returns whether or not this channel object is active
+
+        Returns:
+        True if the channel object is a candidate for events (active)
+        False otherwise
+        """
+        if self._ignore_until_started:
+            return self._started
+        return True
 
     def spawn_call(self, delay=0):
         """Spawn the call!
@@ -286,13 +300,23 @@
         The deferred will pass this object as the parameter.
         """
 
+        def __originate_failure(result):
+            """Handle originate failures.
+
+            Note that this may not be an error, so we just log it and absorb.
+            """
+            LOGGER.info('Originate {0} failed: {1}'.format(
+                self._unique_id, result))
+            return
+
         def __spawn_call_callback(spawn_call_deferred):
             """Actually perform the origination"""
             self.ami.originate(channel=self._channel_name,
                                context=self._controller_context,
                                exten=self._controller_initial_exten,
                                priority='1',
-                               variable={'testuniqueid': '%s' % self._unique_id})
+                               variable={'testuniqueid': '%s' % self._unique_id}).addErrback(__originate_failure)
+            self._started = True
             spawn_call_deferred.callback(self)
 
         spawn_call_deferred = defer.Deferred()
@@ -460,7 +484,7 @@
         have been triggered
         """
 
-        def __start_dtmf(param):
+        def __start_dtmf(_, param):
             """Triggered when the audio has started"""
             dtmf, dtmf_delay, audio_dtmf_deferred = param
             start_deferred = self.send_dtmf(dtmf, dtmf_delay)
@@ -489,6 +513,10 @@
 
     def _new_channel_handler(self, ami, event):
         """Handler for the Newchannel event"""
+
+        if not self._started and self._ignore_until_started:
+            return (ami, event)
+
         if event['channel'] not in self._all_channels:
             self._all_channels.append(event['channel'])
             self._evaluate_candidates()
@@ -496,11 +524,16 @@
 
     def _hangup_event_handler(self, ami, event):
         """Handler for the Hangup event"""
+
+        if not self._started and self._ignore_until_started:
+            return (ami, event)
+
         if self._hungup:
             # Don't process multiple hangup events
-            return
+            return (ami, event)
         if 'channel' not in event:
-            return
+            return (ami, event)
+
         if self.controller_channel == event['channel']:
             LOGGER.debug("Controlling Channel %s hangup event detected" %
                          self.controller_channel)
@@ -509,56 +542,22 @@
                          self.app_channel)
         else:
             # Not us!
-            return
+            return (ami, event)
 
         for observer in self._hangup_observers:
             observer(self, event)
         self._hungup = True
         return (ami, event)
 
-    def _varset_event_handler(self, ami, event):
-        """Handler for the VarSet event
-
-        Note that we only care about the testuniqueid channel variable, which
-        will tell us which channels we're responsible for
-        """
-        if (event['variable'] != 'testuniqueid'):
-            return
-        if (event['value'] != self._unique_id):
-            return
-        channel_name = event['channel'][:len(event['channel'])-2]
-        LOGGER.debug("Detected channel %s" % channel_name)
-        self._candidate_prefix = channel_name
-        self._evaluate_candidates()
-        return (ami, event)
-
-    def _test_event_handler(self, ami, event):
-        """Handler for test events"""
-        if 'channel' not in event:
-            return
-        if self.app_channel not in event['channel'] \
-                and self.controller_channel not in event['channel']:
-            return
-        for observer in self._test_observers:
-            observer(self, event)
-        return (ami, event)
-
-    def _new_exten_handler(self, ami, event):
-        """Handler for new extensions. This figures out which half of a
-        local channel dropped into the specified app"""
-
-        if 'channel' not in event or 'application' not in event:
-            return
-        if event['application'] not in self._applications:
-            return
-        if event['channel'] not in self._candidate_channels:
+    def _evaluate_channel_candidate(self, channel_name):
+        if channel_name not in self._candidate_channels:
             # Whatever channel just entered isn't one of our channels.  This
             # could occur if multiple channels are entering a Conference in a
             # test.
             return
 
-        self.app_channel = event['channel']
-        self._candidate_channels.remove(event['channel'])
+        self.app_channel = channel_name
+        self._candidate_channels.remove(channel_name)
         if (';2' in self.app_channel):
             controller_name = self.app_channel.replace(';2', '') + ';1'
         else:
@@ -567,6 +566,66 @@
         self._candidate_channels.remove(controller_name)
         LOGGER.debug("Setting App Channel to %s; Controlling Channel to %s"
                      % (self.app_channel, self.controller_channel))
+        return
+
+    def _varset_event_handler(self, ami, event):
+        """Handler for the VarSet event
+
+        Note that we only care about the testuniqueid channel variable, which
+        will tell us which channels we're responsible for
+        """
+
+        if not self._started and self._ignore_until_started:
+            return (ami, event)
+
+        def _varset_uniqueid_handler(event):
+            if (event['value'] != self._unique_id):
+                return
+            channel_name = event['channel'][:len(event['channel'])-2]
+            LOGGER.debug("Detected channel %s" % channel_name)
+            self._candidate_prefix = channel_name
+            self._evaluate_candidates()
+
+        def _varset_appchannel_handler(event):
+            self._evaluate_channel_candidate(event['value'])
+            return
+
+        if (event['variable'] == 'testuniqueid'):
+            _varset_uniqueid_handler(event)
+        elif (event['variable'] == 'appchannel'):
+            _varset_appchannel_handler(event)
+
+        return (ami, event)
+
+    def _test_event_handler(self, ami, event):
+        """Handler for test events"""
+
+        if 'channel' not in event:
+            return
+
+        if not self._started and self._ignore_until_started:
+            return (ami, event)
+
+        if self.app_channel not in event['channel'] \
+                and self.controller_channel not in event['channel']:
+            return (ami, event)
+
+        for observer in self._test_observers:
+            observer(self, event)
+        return (ami, event)
+
+    def _new_exten_handler(self, ami, event):
+        """Handler for new extensions. This figures out which half of a
+        local channel dropped into the specified app"""
+
+        if not self._started and self._ignore_until_started:
+            return (ami, event)
+
+        if 'channel' not in event or 'application' not in event:
+            return
+        if event['application'] not in self._applications:
+            return
+        self._evaluate_channel_candidate(event['channel'])
         return (ami, event)
 
 
@@ -626,6 +685,8 @@
             return
 
         self.channel_obj = self.test_object.get_channel_object(self.channel_id)
+        if not self.channel_obj.is_active():
+            return
 
         # Its possible that the event matching could only be so accurate, as
         # there may be multiple Local channel in the same extension.  Make
diff --git a/sample-yaml/apptest-config.yaml.sample b/sample-yaml/apptest-config.yaml.sample
index 95472f5..bb2b3d6 100644
--- a/sample-yaml/apptest-config.yaml.sample
+++ b/sample-yaml/apptest-config.yaml.sample
@@ -80,6 +80,27 @@
             delay: 1                   # Optional. If start-on-create is set,
                                        # the amount of time to wait before
                                        # starting the call.
+            testuniqueid: 'foo'        # Optional. If specified, AppTest will
+                                       # not attempt to match a created Local
+                                       # channel pair itself, and will instead
+                                       # look for a SetVar event over AMI that
+                                       # matches this key (testuniqueid) / value
+                                       # (foo) pair. Upon seeing this event, it
+                                       # will associate the Local channel pair
+                                       # with this channel object definition.
+                                       # This allows the dialplan to control the
+                                       # channel matching, as opposed to
+                                       # matching based on the application under
+                                       # test.
+            ignore-until-started: True # Optional. If set to True, events
+                                       # received for Local channels that would
+                                       # normally match the 'channel-name' are
+                                       # instead ignored. This prevents this
+                                       # channel object from being considered
+                                       # as a 'candidate' for execution until
+                                       # explicitly started via an action, or
+                                       # if the 'start-on-create' is set to
+                                       # True.
             # In general, creating a channel doesn't do much other than just
             # drop a channel into an application and send the controlling
             # half into an indefinite wait.  Once the channel is in the

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia114421b4a30fe39e77a4ed84be4e04cb8f7e8ef
Gerrit-PatchSet: 1
Gerrit-Project: testsuite
Gerrit-Branch: master
Gerrit-Owner: Matt Jordan <mjordan at digium.com>



More information about the asterisk-code-review mailing list