[asterisk-commits] mjordan: testsuite/asterisk/trunk r5541 - in /asterisk/trunk/tests/cdr: ./ cd...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Fri Sep 5 17:31:17 CDT 2014
Author: mjordan
Date: Fri Sep 5 17:31:14 2014
New Revision: 5541
URL: http://svnview.digium.com/svn/testsuite?view=rev&rev=5541
Log:
tests/cdr: Add a multi-party bridge test for CDRs
This patch adds a test for the testsuite that covers CDR generation in
multi-party bridge scenarios. It does the following:
* Makes 5 Local channels, and adds them to a multi-party bridge via ARI.
* It then takes channels 0 and 3 and removes from the bridge
* It then re-adds channels 0 and 3
* Upon having all 5 channels back in the multi-party bridge, it removes them
all, hangs them up, and deletes the bridge.
The test verifies that the expected CDRs are generated.
Review: https://reviewboard.asterisk.org/r/3965/
ASTERISK-24241
Added:
asterisk/trunk/tests/cdr/cdr_bridge_multi/
asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py (with props)
asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/
asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/
asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf (with props)
asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml (with props)
Modified:
asterisk/trunk/tests/cdr/tests.yaml
Added: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py?view=auto&rev=5541
==============================================================================
--- asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py (added)
+++ asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py Fri Sep 5 17:31:14 2014
@@ -1,0 +1,219 @@
+"""
+Test CDRs in a multi-party bridge
+
+Copyright (C) 2014, Digium, Inc.
+Matt Jordan <mjordan at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+
+import logging
+import re
+
+LOGGER = logging.getLogger(__name__)
+
+TEST_STATE = None
+
+# Yay for human sorting:
+# http://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
+def atoi(text):
+ return int(text) if text.isdigit() else text
+
+def natural_keys(text):
+ return [atoi(c) for c in re.split('(\d+)', text)]
+
+class TestState(object):
+ """Base class for the states the test transitions through"""
+
+ def __init__(self, next_state=None):
+ """Constructor
+
+ Keyword Arguments:
+ next_state Class that we should transition to next
+ """
+ self.next_state = next_state
+ self.ari = None
+
+ def _set_ari(self, ari):
+ """Set the ARI object on ourselves"""
+ if not self.ari:
+ self.ari = ari
+
+ def handle_start(self, ari, event, test_object):
+ """Handle a StasisStart event
+
+ Keyword Arguments:
+ ari Our ARI connection back to Asterisk
+ event The StasisStart event
+ test_obejct The one and only test object
+ """
+ self._set_ari(ari)
+ return
+
+ def handle_enter(self, ari, event, test_object):
+ """Handle a ChannelEnteredBridge event
+
+ Keyword Arguments:
+ ari Our ARI connection back to Asterisk
+ event The ChannelEnteredBridge event
+ test_obejct The one and only test object
+ """
+ self._set_ari(ari)
+ return
+
+ def handle_leave(self, ari, event, test_object):
+ """Handle a ChannelLeftBridge event
+
+ Keyword Arguments:
+ ari Our ARI connection back to Asterisk
+ event The ChannelLeftBridge event
+ test_obejct The one and only test object
+ """
+ self._set_ari(ari)
+ return
+
+ def change_state(self, test_object):
+ """Change the test state to the next object
+
+ A concrete implementation should call this when done.
+ This will transition to the next state, or end the test
+ """
+ if not self.next_state:
+ test_object.stop_reactor()
+ else:
+ global TEST_STATE
+ TEST_STATE = self.next_state(ari=self.ari)
+
+class ChannelStartState(TestState):
+ """State that manages getting the channels into the bridge.
+
+ Once all channels are in the bridge, it hands control over
+ to ChannelsInBridge
+ """
+
+ def __init__(self):
+ """Constructor"""
+ super(ChannelStartState, self).__init__(ChannelsInBridge)
+ LOGGER.debug('Test in ChannelStartState')
+
+ self.channels = []
+ self.channels_entered = 0
+
+ def handle_start(self, ari, event, test_object):
+ """Handler for StasisStart"""
+ super(ChannelStartState, self).handle_start(ari, event, test_object)
+
+ self.channels.append(event['channel']['id'])
+ if len(self.channels) < 5:
+ return
+
+ self.channels.sort(key=natural_keys)
+
+ # Make the bridge and put our channels into it. Note that we place
+ # the channels into the bridge in a somewhat haphazard order for "fun",
+ # if we don't make pairings correctly even when channels enter the bridge
+ # in a goofy way, our CDRs will be wrong!
+ ari.post('bridges', bridgeId='cdr_bridge')
+ for ind in [0, 2, 4, 3, 1]:
+ LOGGER.debug('Adding channel %s to bridge' % self.channels[ind])
+ ari.post('bridges', 'cdr_bridge', 'addChannel', channel=self.channels[ind])
+
+ def handle_enter(self, ari, event, test_object):
+ """Handler for ChannelEnteredBridge"""
+ super(ChannelStartState, self).handle_enter(ari, event, test_object)
+ self.channels_entered += 1
+ if (self.channels_entered == 5):
+ self.change_state(test_object)
+
+class ChannelsInBridge(TestState):
+ """State that handles once all the channels are in the bridge.
+
+ This state removes channel 0 and channel 3. Once removed, it
+ puts them back in. Once back in, it transitions to the next
+ state, ChannelsLeaveBridge
+ """
+
+ def __init__(self, ari):
+ """Constructor"""
+ super(ChannelsInBridge, self).__init__(ChannelsLeaveBridge)
+ LOGGER.debug('Test in ChannelsInBridge')
+
+ self.ari = ari
+ self.channels_left = 0
+ self.channels_entered = 0
+ self.channels = self.ari.get('bridges', 'cdr_bridge').json().get('channels')
+ self.channels.sort(key=natural_keys)
+
+ LOGGER.debug('Removing channels %s and %s from bridge' % (self.channels[0], self.channels[3]))
+ self.ari.post('bridges', 'cdr_bridge', 'removeChannel', channel=self.channels[0])
+ self.ari.post('bridges', 'cdr_bridge', 'removeChannel', channel=self.channels[3])
+
+ def handle_leave(self, ari, event, test_object):
+ """Handler for ChannelLeftBridge"""
+ super(ChannelsInBridge, self).handle_leave(ari, event, test_object)
+
+ self.channels_left += 1
+ if (self.channels_left == 2):
+ LOGGER.debug('Adding channels %s and %s to bridge' % (self.channels[3], self.channels[0]))
+ self.ari.post('bridges', 'cdr_bridge', 'addChannel', channel=self.channels[3])
+ self.ari.post('bridges', 'cdr_bridge', 'addChannel', channel=self.channels[0])
+
+ def handle_enter(self, ari, event, test_object):
+ """Handler for ChannelEnteredBridge"""
+ super(ChannelsInBridge, self).handle_enter(ari, event, test_object)
+ self.channels_entered += 1
+ if (self.channels_entered == 2):
+ self.change_state(test_object)
+
+class ChannelsLeaveBridge(TestState):
+ """State that handles all channels leaving the bridge
+
+ This state boots everyone out of the bridge. Once all are
+ out, it hangs them up in order and deletes the bridge. It
+ the ends the test by transitioning to the next state (None)
+ """
+
+ def __init__(self, ari):
+ """Constructor"""
+ super(ChannelsLeaveBridge, self).__init__(None)
+ LOGGER.debug('Test in ChannelsLeaveBridge')
+
+ self.ari = ari
+ self.channels = self.ari.get('bridges', 'cdr_bridge').json().get('channels')
+ self.channels.sort(key=natural_keys)
+ self.left_channels = 0
+
+ for channel in self.channels:
+ LOGGER.debug('Removing channel %s from bridge' % channel)
+ self.ari.post('bridges', 'cdr_bridge', 'removeChannel', channel=channel)
+
+ def handle_leave(self, ari, event, test_object):
+ """Handler for ChannelLeftBridge"""
+ super(ChannelsLeaveBridge, self).handle_leave(ari, event, test_object)
+
+ self.left_channels += 1
+ if (self.left_channels) == 5:
+ for chan in self.channels:
+ LOGGER.debug('Hanging up channel %s' % chan)
+ ari.delete('channels', chan)
+ ari.delete('bridges', 'cdr_bridge')
+ self.change_state(test_object)
+
+TEST_STATE = ChannelStartState()
+
+def on_start(ari, event, test_object):
+ """Handle the StasisStart event"""
+ TEST_STATE.handle_start(ari, event, test_object)
+ return True
+
+def on_enter(ari, event, test_object):
+ """Handle the ChannelEnteredBridge event"""
+ TEST_STATE.handle_enter(ari, event, test_object)
+ return True
+
+def on_leave(ari, event, test_object):
+ """Handle the ChannelLeftBridge event"""
+ TEST_STATE.handle_leave(ari, event, test_object)
+ return True
+
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/cdr_bridge_multi.py
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf?view=auto&rev=5541
==============================================================================
--- asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf (added)
+++ asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf Fri Sep 5 17:31:14 2014
@@ -1,0 +1,10 @@
+[default]
+
+exten => echo,1,NoOp()
+ same => n,Set(CDR_PROP(disable)=True)
+ same => n,Echo()
+
+exten => s,1,NoOp()
+ same => n,Answer()
+ same => n,Stasis(testsuite)
+ same => n,Hangup()
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/configs/ast1/extensions.conf
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml?view=auto&rev=5541
==============================================================================
--- asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml (added)
+++ asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml Fri Sep 5 17:31:14 2014
@@ -1,0 +1,142 @@
+testinfo:
+ summary: Test CDRs generated in a multi-party bridge.
+ description: |
+ Five Local channels are put into a multi-party bridge. The oldest
+ channel (0) and the second to newest (3) are removed from the
+ bridge, then re-added. The test verifies that all expected CDRs
+ are generated between the pairs of channels - both for the original
+ matchings as well as new matchings for channels 0 and 3.
+
+test-modules:
+ add-test-to-search-path: True
+ test-object:
+ config-section: test-object-config
+ typename: ari.AriTestObject
+ modules:
+ -
+ config-section: ari-config
+ typename: ari.WebSocketEventModule
+ -
+ config-section: 'cdr-config'
+ typename: 'cdr.CDRModule'
+
+
+test-object-config:
+ test-iterations:
+ -
+ - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+ - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+ - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+ - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+ - {'channel': 'Local/s at default', 'context': 'default', 'exten': 'echo', 'priority': '1', }
+
+ari-config:
+ apps: testsuite
+ events:
+ - conditions:
+ match:
+ type: StasisStart
+ application: testsuite
+ args: []
+ callback:
+ module: cdr_bridge_multi
+ method: on_start
+ - conditions:
+ match:
+ type: ChannelEnteredBridge
+ application: testsuite
+ callback:
+ module: cdr_bridge_multi
+ method: on_enter
+ - conditions:
+ match:
+ type: ChannelLeftBridge
+ application: testsuite
+ callback:
+ module: cdr_bridge_multi
+ method: on_leave
+
+cdr-config:
+ -
+ file: 'Master'
+ lines:
+ # Channel 0 should have 4 matchings initially
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}2;2'
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}4;2'
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}3;2'
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}1;2'
+ # Channel 0 should have 4 matchings again when it re-joins
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}2;2'
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}4;2'
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}3;2'
+ -
+ channel: 'Local/s at default-.{7}0;2'
+ dchannel: 'Local/s at default-.{7}1;2'
+ # Channel 1 should have 4 matchings: 3 initially, and
+ # one additional when channel 3 re-joins
+ -
+ channel: 'Local/s at default-.{7}1;2'
+ dchannel: 'Local/s at default-.{7}2;2'
+ -
+ channel: 'Local/s at default-.{7}1;2'
+ dchannel: 'Local/s at default-.{7}4;2'
+ -
+ channel: 'Local/s at default-.{7}1;2'
+ dchannel: 'Local/s at default-.{7}3;2'
+ -
+ channel: 'Local/s at default-.{7}1;2'
+ dchannel: 'Local/s at default-.{7}3;2'
+ # Channel 2 should have 3 matchings: 2 initially, and
+ # one additional when channel 3 re-joins
+ -
+ channel: 'Local/s at default-.{7}2;2'
+ dchannel: 'Local/s at default-.{7}4;2'
+ -
+ channel: 'Local/s at default-.{7}2;2'
+ dchannel: 'Local/s at default-.{7}3;2'
+ -
+ channel: 'Local/s at default-.{7}2;2'
+ dchannel: 'Local/s at default-.{7}3;2'
+ # Channel 3 should have 2 matchings: 1 initial matching
+ # and one additional when it re-joins
+ -
+ channel: 'Local/s at default-.{7}3;2'
+ dchannel: 'Local/s at default-.{7}4;2'
+ -
+ channel: 'Local/s at default-.{7}3;2'
+ dchannel: 'Local/s at default-.{7}4;2'
+ # And channel 4 is all by itself (the CDR it has is
+ # from its dialplan execution prior to joining the
+ # bridge)
+ -
+ channel: 'Local/s at default-.{7}4;2'
+ dchannel: ''
+
+properties:
+ minversion: '12.6.0'
+ dependencies:
+ - python: autobahn.websocket
+ - python: requests
+ - python: twisted
+ - python: starpy
+ - asterisk: res_ari_channels
+ - asterisk: res_ari_bridges
+ - asterisk: app_stasis
+ - asterisk: app_echo
+ tags:
+ - ARI
+ - CDR
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: asterisk/trunk/tests/cdr/cdr_bridge_multi/test-config.yaml
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: asterisk/trunk/tests/cdr/tests.yaml
URL: http://svnview.digium.com/svn/testsuite/asterisk/trunk/tests/cdr/tests.yaml?view=diff&rev=5541&r1=5540&r2=5541
==============================================================================
--- asterisk/trunk/tests/cdr/tests.yaml (original)
+++ asterisk/trunk/tests/cdr/tests.yaml Fri Sep 5 17:31:14 2014
@@ -4,6 +4,7 @@
- test: 'console_dial_sip_busy'
- test: 'console_dial_sip_congestion'
- test: 'cdr_originate_sip_congestion_log'
+ - test: 'cdr_bridge_multi'
- test: 'console_dial_sip_transfer'
- test: 'cdr_unanswered_yes'
# Temporarily disabled while failures are debugged
More information about the asterisk-commits
mailing list