1
# $Id: mod_sipp.py 4188 2012-06-29 09:01:17Z nanang $
3
## Automatic test module for SIPp.
5
## This module will need a test driver for each SIPp scenario:
6
## - For simple scenario, i.e: make/receive call (including auth), this
7
## test module can auto-generate a default test driver, i.e: make call
8
## or apply auto answer. Just name the SIPp scenario using "uas" or
9
## "uac" prefix accordingly.
10
## - Custom test driver can be defined in a python script file containing
11
## a list of the PJSUA instances and another list for PJSUA expects/
12
## commands. The custom test driver file must use the same filename as
13
## the SIPp XML scenario. See samples of SIPp scenario + its driver
14
## in tests/pjsua/scripts-sipp/ folder for detail.
16
## Here are defined macros that can be used in the custom driver:
17
## - $SIPP_PORT : SIPp binding port
18
## - $SIPP_URI : SIPp SIP URI
19
## - $PJSUA_PORT[N] : binding port of PJSUA instance #N
20
## - $PJSUA_URI[N] : SIP URI of PJSUA instance #N
32
# flags that test is running in Unix
34
if sys.platform.lower().find("win32")!=-1 or sys.platform.lower().find("microsoft")!=-1:
39
# /dev/null handle, for redirecting output when SIPP is not in background mode
42
# SIPp executable path and param
43
#SIPP_PATH = '"C:\\Program Files (x86)\\Sipp_3.2\\sipp.exe"'
46
SIPP_PARAM = "-m 1 -i 127.0.0.1 -p " + str(SIPP_PORT)
48
# On BG mode, SIPp doesn't require special terminal
49
# On non-BG mode, on win, it needs env var: "TERMINFO=c:\cygwin\usr\share\terminfo"
50
# TODO: on unix with BG mode, waitpid() always fails, need to be fixed
52
#SIPP_BG_MODE = not G_INUNIX
54
# Will be updated based on the test driver file (a .py file whose the same name as SIPp XML file)
58
# Default PJSUA param if test driver is not available:
59
# - no-tcp as SIPp is on UDP only
60
# - id, username, and realm: to allow PJSUA sending re-INVITE with auth after receiving 401/407 response
61
PJSUA_DEF_PARAM = "--null-audio --max-calls=1 --no-tcp --id=sip:a@localhost --username=a --realm=*"
63
# Get SIPp scenario (XML file)
65
if ARGS[1].endswith('.xml'):
66
SIPP_SCEN_XML = ARGS[1]
71
# Functions for resolving macros in the test driver
72
def resolve_pjsua_port(mo):
73
return str(PJSUA_INST_PARAM[int(mo.group(1))].sip_port)
75
def resolve_pjsua_uri(mo):
76
return PJSUA_INST_PARAM[int(mo.group(1))].uri[1:-1]
78
def resolve_driver_macros(st):
79
st = re.sub("\$SIPP_PORT", str(SIPP_PORT), st)
80
st = re.sub("\$SIPP_URI", "sip:sipp@127.0.0.1:"+str(SIPP_PORT), st)
81
st = re.sub("\$PJSUA_PORT\[(\d+)\]", resolve_pjsua_port, st)
82
st = re.sub("\$PJSUA_URI\[(\d+)\]", resolve_pjsua_uri, st)
87
if os.access(SIPP_SCEN_XML[:-4]+".py", os.R_OK):
88
# Load test driver file (the corresponding .py file), if any
89
cfg_file = imp.load_source("cfg_file", SIPP_SCEN_XML[:-4]+".py")
90
for ua_idx, ua_param in enumerate(cfg_file.PJSUA):
91
ua_param = resolve_driver_macros(ua_param)
92
PJSUA_INST_PARAM.append(InstanceParam("pjsua"+str(ua_idx), ua_param))
93
PJSUA_EXPECTS = cfg_file.PJSUA_EXPECTS
95
# Generate default test driver
96
if os.path.basename(SIPP_SCEN_XML)[0:3] == "uas":
97
# auto make call when SIPp is as UAS
98
ua_param = PJSUA_DEF_PARAM + " sip:127.0.0.1:" + str(SIPP_PORT)
100
# auto answer when SIPp is as UAC
101
ua_param = PJSUA_DEF_PARAM + " --auto-answer=200"
102
PJSUA_INST_PARAM.append(InstanceParam("pjsua", ua_param))
105
# Start SIPp process, returning PID
110
sipp_param = SIPP_PARAM + " -sf " + SIPP_SCEN_XML
112
sipp_param = sipp_param + " -bg"
114
sipp_param = sipp_param + " -timeout "+str(SIPP_TIMEOUT)+"s -timeout_error" + " -deadcall_wait "+str(SIPP_TIMEOUT)+"s"
117
sipp_param = sipp_param + " 127.0.0.1:" + str(PJSUA_INST_PARAM[0].sip_port)
120
fullcmd = os.path.normpath(SIPP_PATH) + " " + sipp_param
121
print "Running SIPP: " + fullcmd
123
sipp_proc = subprocess.Popen(fullcmd, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=G_INUNIX, universal_newlines=False)
125
# redirect output to NULL
127
#FDEVNULL = open(os.devnull, 'w')
128
FDEVNULL = open("logs/sipp_output.tmp", 'w')
129
sipp_proc = subprocess.Popen(fullcmd, shell=G_INUNIX, stdout=FDEVNULL, stderr=FDEVNULL)
132
if sipp_proc == None or sipp_proc.poll():
137
# get SIPp child process PID
139
r = re.compile("PID=\[(\d+)\]", re.I)
142
line = sipp_proc.stdout.readline()
143
pid_r = r.search(line)
145
pid = int(pid_r.group(1))
147
if not sipp_proc.poll():
151
# Win specific: get process handle from PID, as on win32, os.waitpid() takes process handle instead of pid
152
if (sys.platform == "win32"):
153
SYNCHRONIZE = 0x00100000
154
PROCESS_QUERY_INFORMATION = 0x0400
155
hnd = ctypes.windll.kernel32.OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, False, pid)
161
# Wait SIPp process to exit, returning SIPp exit code
167
return sipp.returncode
170
print "Waiting SIPp (PID=" + str(sipp) + ") to exit.."
174
wait_cnt = wait_cnt + 1
175
[pid_, ret_code] = os.waitpid(sipp, 0)
177
#print "SIPP returned ", ret_code
178
ret_code = ret_code >> 8
180
# Win specific: Close process handle
181
if (sys.platform == "win32"):
182
ctypes.windll.kernel32.CloseHandle(sipp)
187
print "Retry ("+str(wait_cnt)+") waiting SIPp.."
193
def exec_pjsua_expects(t, sipp):
194
# Get all PJSUA instances
196
for ua_idx in range(len(PJSUA_INST_PARAM)):
197
ua.append(t.process[ua_idx])
200
while len(PJSUA_EXPECTS):
201
expect = PJSUA_EXPECTS.pop(0)
203
expect_st = expect[1]
204
send_cmd = resolve_driver_macros(expect[2])
205
# Handle exception in pjsua flow, to avoid zombie SIPp process
208
ua[ua_idx].expect(expect_st, raise_on_error = True)
210
ua[ua_idx].send(send_cmd)
215
ua_err_st = "Unknown error"
218
# Need to poll here for handling these cases:
219
# - If there is no PJSUA EXPECT scenario, we must keep polling the stdout,
220
# otherwise PJSUA process may stuck (due to stdout pipe buffer full?).
221
# - last PJSUA_EXPECT contains a pjsua command that needs time to
222
# finish, for example "v" (re-INVITE), the SIPp XML scenario may expect
223
# that re-INVITE transaction to be completed and without stdout poll
224
# PJSUA process may stuck.
225
# Ideally the poll should be done contiunously until SIPp process is
227
for ua_idx in range(len(ua)):
228
ua[ua_idx].expect(inc_const.STDOUT_REFRESH, raise_on_error = False)
233
def sipp_err_to_str(err_code):
235
return "All calls were successful"
237
return "At least one call failed"
239
return "exit on internal command. Calls may have been processed"
241
return "Normal exit without calls processed"
243
return "Fatal error (timeout)"
245
return "Fatal error binding a socket"
247
return "Unknown error"
258
raise TestError("Failed starting SIPp")
260
ua_err_st = exec_pjsua_expects(t, sipp)
262
sipp_ret_code = wait_sipp(sipp)
265
raise TestError(ua_err_st)
268
rc = ctypes.c_byte(sipp_ret_code).value
269
raise TestError("SIPp returned error " + str(rc) + ": " + sipp_err_to_str(rc))
272
# Here where it all comes together
273
test = TestParam(SIPP_SCEN_XML[:-4],