#9621: Implement conference functional tests

This commit is contained in:
Alexandre Savard
2012-04-15 10:27:51 -04:00
parent 00126a3611
commit ced6c682d2
5 changed files with 408 additions and 94 deletions

View File

@ -59,11 +59,15 @@ class SflPhoneCtrl(Thread):
onCallCurrent_cb onCallCurrent_cb
onCallBusy_cb onCallBusy_cb
onCallFailure_cb onCallFailure_cb
onConferenceCreated_cb
""" """
# list of active calls (known by the client) # list of active calls (known by the client)
activeCalls = {} activeCalls = {}
# list of active conferences
activeConferences = {}
def __init__(self, name=sys.argv[0]): def __init__(self, name=sys.argv[0]):
Thread.__init__(self) Thread.__init__(self)
@ -74,6 +78,7 @@ class SflPhoneCtrl(Thread):
self.name = name self.name = name
self.currentCallId = "" self.currentCallId = ""
self.currentConfId = ""
self.isStop = False self.isStop = False
@ -145,6 +150,7 @@ class SflPhoneCtrl(Thread):
print "Adding Incoming call method" print "Adding Incoming call method"
proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall) proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall)
proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged) proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged)
proxy_callmgr.connect_to_signal('conferenceCreated', self.onConferenceCreated)
except dbus.DBusException, e: except dbus.DBusException, e:
print e print e
@ -171,7 +177,7 @@ class SflPhoneCtrl(Thread):
def onIncomingCall_cb(self): def onIncomingCall_cb(self):
pass pass
def onCallHangup_cb(self): def onCallHangup_cb(self, callId):
pass pass
def onCallRinging_cb(self): def onCallRinging_cb(self):
@ -202,7 +208,7 @@ class SflPhoneCtrl(Thread):
def onCallHangUp(self, callid): def onCallHangUp(self, callid):
""" Remove callid from call list """ """ Remove callid from call list """
self.onCallHangup_cb() self.onCallHangup_cb(callid)
self.currentCallId = "" self.currentCallId = ""
del self.activeCalls[callid] del self.activeCalls[callid]
@ -274,6 +280,12 @@ class SflPhoneCtrl(Thread):
else: else:
print "unknown state" print "unknown state"
def onConferenceCreated_cb(self):
pass
def onConferenceCreated(self, confId):
self.currentConfId = confId
self.onConferenceCreated_cb()
# #
# Account management # Account management
@ -545,9 +557,6 @@ class SflPhoneCtrl(Thread):
for call in self.activeCalls: for call in self.activeCalls:
print "\t" + call print "\t" + call
#
# Action
#
def Call(self, dest): def Call(self, dest):
"""Start a call and return a CallID """Start a call and return a CallID
@ -636,12 +645,6 @@ class SflPhoneCtrl(Thread):
def Hold(self, callid): def Hold(self, callid):
"""Hold a call identified by a CallID""" """Hold a call identified by a CallID"""
# if not self.account:
# self.setFirstRegisteredAccount()
# if not self.isAccountRegistered():
# raise SflPhoneError("Can't hold a call without a registered account")
if callid is None or callid == "": if callid is None or callid == "":
raise SflPhoneError("Invalid callID") raise SflPhoneError("Invalid callID")
@ -651,12 +654,6 @@ class SflPhoneCtrl(Thread):
def UnHold(self, callid): def UnHold(self, callid):
"""Unhold an incoming call identified by a CallID""" """Unhold an incoming call identified by a CallID"""
# if not self.account:
# self.setFirstRegisteredAccount()
# if not self.isAccountRegistered():
# raise SflPhoneError("Can't unhold a call without a registered account")
if callid is None or callid == "": if callid is None or callid == "":
raise SflPhoneError("Invalid callID") raise SflPhoneError("Invalid callID")
@ -679,13 +676,30 @@ class SflPhoneCtrl(Thread):
callid = m.hexdigest() callid = m.hexdigest()
return callid return callid
def createConference(self, call1Id, call2Id):
""" Create a conference given the two call ids """
self.callmanager.joinParticipant(call1Id, call2Id)
def hangupConference(self, confId):
""" Hang up each call for this conference """
self.callmanager.hangUpConference(confId)
def run(self): def run(self):
"""Processing method for this thread""" """Processing method for this thread"""
context = self.loop.get_context() context = self.loop.get_context()
while True: while True:
context.iteration(True) context.iteration(True)
if self.isStop: if self.isStop:
print "++++++++++++++++++++++++++++++++++++++++"
print "++++++++++++++++++++++++++++++++++++++++"
print "++++++++++++++++++++++++++++++++++++++++"
print "++++++++++++++++++++++++++++++++++++++++"
return return

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# Copyright (C) 2009 by the Free Software Foundation, Inc. # Copyright (C) 2012 by the Free Software Foundation, Inc.
#
# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -22,7 +24,7 @@ class SippWrapper:
""" Wrapper taht allow for managing sipp command line easily """ """ Wrapper taht allow for managing sipp command line easily """
def __init__(self): def __init__(self):
self.commandLine = "sipp" self.commandLine = "./sipp"
self.remoteServer = "" self.remoteServer = ""
self.remotePort = "" self.remotePort = ""
self.localInterface = "" self.localInterface = ""
@ -41,9 +43,11 @@ class SippWrapper:
self.enableTraceRtt = False self.enableTraceRtt = False
self.enableTraceLogs = False self.enableTraceLogs = False
def buildCommandLine(self): def buildCommandLine(self, port):
""" Fill the command line arguments based on specified parameters """ """ Fill the command line arguments based on specified parameters """
self.localPort = str(port)
if not self.remotePort and not self.remoteServer: if not self.remotePort and not self.remoteServer:
self.isUserAgentClient = False self.isUserAgentClient = False
elif self.remotePort and not self.remoteServer: elif self.remotePort and not self.remoteServer:
@ -106,9 +110,8 @@ class SippWrapper:
def launch(self): def launch(self):
""" Launch the sipp instance using the specified arguments """ """ Launch the sipp instance using the specified arguments """
self.buildCommandLine()
print self.commandLine print self.commandLine
return os.system(self.commandLine) return os.system(self.commandLine + " 2>&1 > /dev/null")
class SippScreenStatParser: class SippScreenStatParser:

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/USR/BIN/ENV PYTHON
# #
# Copyright (C) 2009 by the Free Software Foundation, Inc. # Copyright (C) 2012 by the Free Software Foundation, Inc.
#
# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -18,7 +20,9 @@
import os import os
import time import time
import yaml
import logging import logging
import multiprocessing
from sippwrap import SippWrapper from sippwrap import SippWrapper
from sippwrap import SippScreenStatParser from sippwrap import SippScreenStatParser
from sflphonectrl import SflPhoneCtrl from sflphonectrl import SflPhoneCtrl
@ -29,10 +33,62 @@ from nose.tools import nottest
### function starting with 'test' are executed. ### function starting with 'test' are executed.
### ###
accountList = ["IP2IP", "Account:1332798167"]
SCENARIO_PATH = "../sippxml/" SCENARIO_PATH = "../sippxml/"
class SippCtrl:
def __init__(self):
self.remoteServer = "127.0.0.1"
self.remotePort = str(5062)
self.localInterface = "127.0.0.1"
self.localPort = str(5060)
def initialize_sipp_registration_instance(self, instance, xmlScenario):
instance.remoteServer = self.remoteServer
instance.remotePort = self.remotePort
instance.localInterface = self.localInterface
instance.localPort = self.localPort
instance.customScenarioFile = SCENARIO_PATH + xmlScenario
instance.numberOfCall = 1
instance.numberOfSimultaneousCall = 1
def initialize_sipp_call_instance(self, instance):
instance.localInterface = self.localInterface
instance.localPort = self.localPort
instance.numberOfCall = 1
instance.numberOfSimultaneousCall = 1
instance.enableTraceScreen = True
def launchSippProcess(self, sippInstance, localPort):
sippInstance.buildCommandLine(localPort)
sippInstance.launch()
def find_sipp_pid(self):
# Retreive the PID of the last
# The /proc/PID/cmdline contain the command line from
pids = [int(x) for x in os.listdir("/proc") if x.isdigit()]
sippPid = [pid for pid in pids if "sipp" in open("/proc/" + str(pid) + "/cmdline").readline()]
return sippPid[0]
def clean_log_directory(self):
dirlist = os.listdir("./")
files = [x for x in dirlist if "screen.log" in x]
for f in files:
os.remove(f)
def parse_results(self):
dirlist = os.listdir("./")
logfile = [x for x in dirlist if "screen.log" in x]
fullpath = os.path.dirname(os.path.realpath(__file__)) + "/"
# there should be only one screen.log file (see clean_log_directory)
resultParser = SippScreenStatParser(fullpath + logfile[0])
assert(not resultParser.isAnyFailedCall())
assert(resultParser.isAnySuccessfulCall())
class TestSFLPhoneAccountConfig(SflPhoneCtrl): class TestSFLPhoneAccountConfig(SflPhoneCtrl):
""" The test suite for account configuration """ """ The test suite for account configuration """
@ -46,14 +102,40 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl):
self.logger.addHandler(filehdlr) self.logger.addHandler(filehdlr)
self.logger.setLevel(logging.INFO) self.logger.setLevel(logging.INFO)
@nottest
def get_config(self):
""" Parsse configuration file and return a dictionary """
config = {}
with open("sflphoned.functest.yml","r") as stream:
config = yaml.load(stream)
return config
def get_account_list_from_config(self):
""" Get the accout list from config and add IP2IP """
config = self.get_config()
accounts = config["preferences"]["order"]
accountList = accounts.split('/')
del accountList[len(accountList)-1]
accountList.append("IP2IP")
return accountList
def test_get_account_list(self): def test_get_account_list(self):
self.logger.info("Test get account list") self.logger.info("Test get account list")
accountList = self.get_account_list_from_config()
# make sure that the intersection between the list is of same size
accList = self.getAllAccounts() accList = self.getAllAccounts()
listIntersection = set(accList) & set(accountList) listIntersection = set(accList) & set(accountList)
assert len(listIntersection) == len(accountList) assert len(listIntersection) == len(accountList)
@nottest
def test_account_registration(self): def test_account_registration(self):
self.logger.info("Test account registration") self.logger.info("Test account registration")
accList = [x for x in self.getAllAccounts() if x != "IP2IP"] accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
@ -72,6 +154,24 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl):
assert self.isAccountRegistered(acc) assert self.isAccountRegistered(acc)
@nottest
def test_get_account_details(self):
self.logger.info("Test account details")
accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
config = self.get_config()
accountDetails = {}
for acc in accList:
accountDetails[acc] = self.getAccountDetails(acc)
accountConfDetails = {}
for accConf in config["accounts"]:
accountConfDetails[accConf["id"]] = accConf
@nottest @nottest
def test_add_remove_account(self): def test_add_remove_account(self):
self.logger.info("Test add/remove account") self.logger.info("Test add/remove account")
@ -99,11 +199,12 @@ class TestSFLPhoneAccountConfig(SflPhoneCtrl):
class TestSFLPhoneRegisteredCalls(SflPhoneCtrl): class TestSFLPhoneRegisteredCalls(SflPhoneCtrl, SippCtrl):
""" The test suite for call interaction """ """ The test suite for call interaction """
def __init__(self): def __init__(self):
SflPhoneCtrl.__init__(self) SflPhoneCtrl.__init__(self)
SippCtrl.__init__(self)
self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls") self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls")
filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log") filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log")
@ -113,8 +214,6 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl):
self.logger.setLevel(logging.INFO) self.logger.setLevel(logging.INFO)
self.sippRegistrationInstance = SippWrapper() self.sippRegistrationInstance = SippWrapper()
self.sippCallInstance = SippWrapper() self.sippCallInstance = SippWrapper()
self.localInterface = "127.0.0.1"
self.localPort = str(5064)
# Make sure the test directory is populated with most recent log files # Make sure the test directory is populated with most recent log files
self.clean_log_directory() self.clean_log_directory()
@ -143,70 +242,26 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl):
self.stopThread() self.stopThread()
def clean_log_directory(self):
dirlist = os.listdir("./")
files = [x for x in dirlist if "screen.log" in x]
for f in files:
os.remove(f)
def find_sipp_pid(self):
# Retreive the PID of the last
# The /proc/PID/cmdline contain the command line from
pids = [int(x) for x in os.listdir("/proc") if x.isdigit()]
sippPid = [pid for pid in pids if "sipp" in open("/proc/" + str(pid) + "/cmdline").readline()]
return sippPid[0]
def parse_results(self):
dirlist = os.listdir("./")
logfile = [x for x in dirlist if "screen.log" in x]
print logfile
fullpath = os.path.dirname(os.path.realpath(__file__)) + "/"
# there should be only one screen.log file (see clean_log_directory)
resultParser = SippScreenStatParser(fullpath + logfile[0])
assert(not resultParser.isAnyFailedCall())
assert(resultParser.isAnySuccessfulCall())
def test_registered_call(self): def test_registered_call(self):
self.logger.info("Test Registered Call") self.logger.info("Test Registered Call")
# launch the sipp instance in background # Launch a sipp instance for account registration on asterisk
# sipp 127.0.0.1:5060 -sf uac_register_no_cvs.xml -i 127.0.0.1 -p 5062 # this account will then be used to receive call from sflphone
self.sippRegistrationInstance.remoteServer = "127.0.0.1" self.initialize_sipp_registration_instance(self.sippRegistrationInstance, "uac_register_no_cvs_300.xml")
self.sippRegistrationInstance.remotePort = str(5062) regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess,
self.sippRegistrationInstance.localInterface = self.localInterface args=(self.sippRegistrationInstance, 5064,))
self.sippRegistrationInstance.localPort = self.localPort regd.start()
self.sippRegistrationInstance.customScenarioFile = SCENARIO_PATH + "uac_register_no_cvs.xml"
self.sippRegistrationInstance.launchInBackground = True
self.sippRegistrationInstance.numberOfCall = 1
self.sippRegistrationInstance.numberOfSimultaneousCall = 1
self.sippRegistrationInstance.launch() # wait for the registration to complete
regd.join()
# wait for this instance of sipp to complete registration # Launch a sipp instance waiting for a call from previously registered account
sippPid = self.find_sipp_pid() self.initialize_sipp_call_instance(self.sippCallInstance)
while os.path.exists("/proc/" + str(sippPid)): calld = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess,
time.sleep(1) args=(self.sippCallInstance, 5064,))
calld.start()
# sipp -sn uas -p 5062 -i 127.0.0.1 # Make sure every account are enabled
self.sippCallInstance.localInterface = self.localInterface
self.sippCallInstance.localPort = self.localPort
self.sippCallInstance.launchInBackground = True
self.sippCallInstance.numberOfCall = 1
self.sippCallInstance.numberOfSimultaneousCall = 1
self.sippCallInstance.enableTraceScreen = True
self.sippCallInstance.launch()
sippPid = self.find_sipp_pid()
# make sure every account are enabled
accList = [x for x in self.getAllAccounts() if x != "IP2IP"] accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
for acc in accList: for acc in accList:
if not self.isAccountRegistered(acc): if not self.isAccountRegistered(acc):
@ -215,11 +270,143 @@ class TestSFLPhoneRegisteredCalls(SflPhoneCtrl):
# Make a call to the SIPP instance # Make a call to the SIPP instance
self.Call("300") self.Call("300")
# Start Glib mainloop to process callbacks # Start the threaded loop to handle GLIB cllbacks
self.start() self.start()
# Wait the sipp instance to dump log files # Wait for the sipp instance to dump log files
while os.path.exists("/proc/" + str(sippPid)): calld.join()
time.sleep(1)
self.stopThread()
self.parse_results() self.parse_results()
class TestSFLPhoneConferenceCalls(SflPhoneCtrl, SippCtrl):
""" Test Conference calls """
def __init__(self):
SflPhoneCtrl.__init__(self)
SippCtrl.__init__(self)
self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls")
filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log")
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
filehdlr.setFormatter(formatter)
self.logger.addHandler(filehdlr)
self.logger.setLevel(logging.INFO)
self.sippRegistrationInstanceA = SippWrapper()
self.sippRegistrationInstanceB = SippWrapper()
self.sippCallInstanceA = SippWrapper()
self.sippCallInstanceB = SippWrapper()
self.localPortCallA = str(5064)
self.localPortCallB = str(5066)
self.callCount = 0
self.accountCalls = []
# Make sure the test directory is populated with most recent log files
# self.clean_log_directory()
def onCallCurrent_cb(self):
""" On incoming call, answer the call, then hangup """
self.callCount += 1
self.accountCalls.append(self.currentCallId)
print "Account List: ", str(self.accountCalls)
if self.callCount == 2:
self.createConference(self.accountCalls[0], self.accountCalls[1])
def onCallRinging_cb(self):
""" Display messages when call is ringing """
print "The call is ringing"
def onCallHangup_cb(self, callId):
""" Exit thread when all call are finished """
if callId in self.accountCalls:
self.accountCalls.remove(callId)
self.callCount -= 1
if self.callCount == 0:
self.stopThread()
def onCallFailure_cb(self):
""" If a failure occurs duing the call, just leave the running thread """
print "Stopping Thread"
self.stopThread()
def onConferenceCreated_cb(self):
""" Called once the conference is created """
print "Conference Created ", self.currentConfId
print "Conference Hangup ", self.currentConfId
self.hangupConference(self.currentConfId)
def test_conference_call(self):
self.logger.info("Test Registered Call")
# launch the sipp instance to register the first participant to astersik
self.initialize_sipp_registration_instance(self.sippRegistrationInstanceA, "uac_register_no_cvs_300.xml")
regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess,
args=(self.sippRegistrationInstanceA, 5064,))
regd.start()
regd.join()
# launch the sipp instance to register the second participant to asterisk
self.initialize_sipp_registration_instance(self.sippRegistrationInstanceB, "uac_register_no_cvs_400.xml")
regd = multiprocessing.Process(name='sipp2register', target=self.launchSippProcess,
args=(self.sippRegistrationInstanceB, 5066,))
regd.start()
regd.join()
# launch the sipp instance waining for call as the first participant
self.initialize_sipp_call_instance(self.sippCallInstanceA)
calldA = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess,
args=(self.sippCallInstanceA, 5064,))
calldA.start()
# launch the sipp instance waiting for call as the second particpant
self.initialize_sipp_call_instance(self.sippCallInstanceB)
calldB = multiprocessing.Process(name='sipp2call', target=self.launchSippProcess,
args=(self.sippCallInstanceB, 5066,))
calldB.start()
# make sure every account are enabled
accList = [x for x in self.getAllAccounts() if x != "IP2IP"]
for acc in accList:
if not self.isAccountRegistered(acc):
self.setAccountEnable(acc, True)
# make a call to the SIPP instance
self.Call("300")
self.Call("400")
# start the main loop for processing glib callbacks
self.start()
print "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"
calldA.join()
print "+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+"
calldB.join()
print "====================================================="
self.stopThread()
self.parse_results()
# callInstance = TestSFLPhoneRegisteredCalls()
# callInstance.test_registered_call()
confInstance = TestSFLPhoneConferenceCalls()
confInstance.test_conference_call()

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="ISO-8859-2" ?>
<!-- Use with CSV file struct like: 3000;192.168.1.106;[authentication username=3000 password=3000];
(user part of uri, server address, auth tag in each line)
-->
<scenario name="register_client">
<send retrans="500">
<![CDATA[
REGISTER sip:127.0.0.1 SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: <sip:300@127.0.0.1>;tag=[call_number]
To: <sip:400@127.0.0.1>
Call-ID: [call_id]
CSeq: [cseq] REGISTER
Contact: sip:300@[local_ip]:[local_port]
Max-Forwards: 10
Expires: 120
User-Agent: SIPp/Win32
Content-Length: 0
]]>
</send>
<!-- asterisk -->
<recv response="200">
<!--
<action>
<ereg regexp=".*" search_in="hdr" header="Contact:" check_it="true" assign_to="1" />
</action>
-->
</recv>
<!--
<recv request="INVITE" crlf="true">
</recv>
<send>
<![CDATA[
SIP/2.0 180 Ringing
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
]]>
</send>
<send retrans="500">
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
<recv request="ACK"
optional="true"
rtd="true"
crlf="true">
</recv>
<recv request="BYE">
</recv>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
]]>
</send>
<timewait milliseconds="4000"/>
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
-->
</scenario>