blob: c705c96682aaf128f2909efb8fd8164c1fed76d4 [file] [log] [blame]
Alexandre Lision67916dd2014-01-24 13:33:04 -05001# $Id$
2
3## Automatic test module for SIPp.
4##
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.
15##
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
21
22import ctypes
23import time
24import imp
25import sys
26import os
27import re
28import subprocess
29from inc_cfg import *
30import inc_const
31
32# flags that test is running in Unix
33G_INUNIX = False
34if sys.platform.lower().find("win32")!=-1 or sys.platform.lower().find("microsoft")!=-1:
35 G_INUNIX = False
36else:
37 G_INUNIX = True
38
39# /dev/null handle, for redirecting output when SIPP is not in background mode
40FDEVNULL = None
41
42# SIPp executable path and param
43#SIPP_PATH = '"C:\\Program Files (x86)\\Sipp_3.2\\sipp.exe"'
44SIPP_PATH = 'sipp'
45SIPP_PORT = 6000
46SIPP_PARAM = "-m 1 -i 127.0.0.1 -p " + str(SIPP_PORT)
47SIPP_TIMEOUT = 60
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
51SIPP_BG_MODE = False
52#SIPP_BG_MODE = not G_INUNIX
53
54# Will be updated based on the test driver file (a .py file whose the same name as SIPp XML file)
55PJSUA_INST_PARAM = []
56PJSUA_EXPECTS = []
57
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
61PJSUA_DEF_PARAM = "--null-audio --max-calls=1 --no-tcp --id=sip:a@localhost --username=a --realm=*"
62
63# Get SIPp scenario (XML file)
64SIPP_SCEN_XML = ""
65if ARGS[1].endswith('.xml'):
66 SIPP_SCEN_XML = ARGS[1]
67else:
68 exit(-99)
69
70
71# Functions for resolving macros in the test driver
72def resolve_pjsua_port(mo):
73 return str(PJSUA_INST_PARAM[int(mo.group(1))].sip_port)
74
75def resolve_pjsua_uri(mo):
76 return PJSUA_INST_PARAM[int(mo.group(1))].uri[1:-1]
77
78def 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)
83 return st
84
85
86# Init test driver
87if 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
94else:
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)
99 else:
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))
103
104
105# Start SIPp process, returning PID
106def start_sipp():
107 global SIPP_BG_MODE
108 sipp_proc = None
109
110 sipp_param = SIPP_PARAM + " -sf " + SIPP_SCEN_XML
111 if SIPP_BG_MODE:
112 sipp_param = sipp_param + " -bg"
113 if SIPP_TIMEOUT:
114 sipp_param = sipp_param + " -timeout "+str(SIPP_TIMEOUT)+"s -timeout_error" + " -deadcall_wait "+str(SIPP_TIMEOUT)+"s"
115
116 # add target param
117 sipp_param = sipp_param + " 127.0.0.1:" + str(PJSUA_INST_PARAM[0].sip_port)
118
119 # run SIPp
120 fullcmd = os.path.normpath(SIPP_PATH) + " " + sipp_param
121 print "Running SIPP: " + fullcmd
122 if SIPP_BG_MODE:
123 sipp_proc = subprocess.Popen(fullcmd, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=G_INUNIX, universal_newlines=False)
124 else:
125 # redirect output to NULL
126 global FDEVNULL
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)
130
131 if not SIPP_BG_MODE:
132 if sipp_proc == None or sipp_proc.poll():
133 return None
134 return sipp_proc
135
136 else:
137 # get SIPp child process PID
138 pid = 0
139 r = re.compile("PID=\[(\d+)\]", re.I)
140
141 while True:
142 line = sipp_proc.stdout.readline()
143 pid_r = r.search(line)
144 if pid_r:
145 pid = int(pid_r.group(1))
146 break
147 if not sipp_proc.poll():
148 break
149
150 if pid != 0:
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)
156 pid = hnd
157
158 return pid
159
160
161# Wait SIPp process to exit, returning SIPp exit code
162def wait_sipp(sipp):
163 if not SIPP_BG_MODE:
164 global FDEVNULL
165 sipp.wait()
166 FDEVNULL.close()
167 return sipp.returncode
168
169 else:
170 print "Waiting SIPp (PID=" + str(sipp) + ") to exit.."
171 wait_cnt = 0
172 while True:
173 try:
174 wait_cnt = wait_cnt + 1
175 [pid_, ret_code] = os.waitpid(sipp, 0)
176 if sipp == pid_:
177 #print "SIPP returned ", ret_code
178 ret_code = ret_code >> 8
179
180 # Win specific: Close process handle
181 if (sys.platform == "win32"):
182 ctypes.windll.kernel32.CloseHandle(sipp)
183
184 return ret_code
185 except os.error:
186 if wait_cnt <= 5:
187 print "Retry ("+str(wait_cnt)+") waiting SIPp.."
188 else:
189 return -99
190
191
192# Execute PJSUA flow
193def exec_pjsua_expects(t, sipp):
194 # Get all PJSUA instances
195 ua = []
196 for ua_idx in range(len(PJSUA_INST_PARAM)):
197 ua.append(t.process[ua_idx])
198
199 ua_err_st = ""
200 while len(PJSUA_EXPECTS):
201 expect = PJSUA_EXPECTS.pop(0)
202 ua_idx = expect[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
206 try:
207 if expect_st != "":
208 ua[ua_idx].expect(expect_st, raise_on_error = True)
209 if send_cmd != "":
210 ua[ua_idx].send(send_cmd)
211 except TestError, e:
212 ua_err_st = e.desc
213 break;
214 except:
215 ua_err_st = "Unknown error"
216 break;
217
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
226 # terminated.
227 for ua_idx in range(len(ua)):
228 ua[ua_idx].expect(inc_const.STDOUT_REFRESH, raise_on_error = False)
229
230 return ua_err_st
231
232
233def sipp_err_to_str(err_code):
234 if err_code == 0:
235 return "All calls were successful"
236 elif err_code == 1:
237 return "At least one call failed"
238 elif err_code == 97:
239 return "exit on internal command. Calls may have been processed"
240 elif err_code == 99:
241 return "Normal exit without calls processed"
242 elif err_code == -1:
243 return "Fatal error (timeout)"
244 elif err_code == -2:
245 return "Fatal error binding a socket"
246 else:
247 return "Unknown error"
248
249
250# Test body function
251def TEST_FUNC(t):
252
253 sipp_ret_code = 0
254 ua_err_st = ""
255
256 sipp = start_sipp()
257 if not sipp:
258 raise TestError("Failed starting SIPp")
259
260 ua_err_st = exec_pjsua_expects(t, sipp)
261
262 sipp_ret_code = wait_sipp(sipp)
263
264 if ua_err_st != "":
265 raise TestError(ua_err_st)
266
267 if sipp_ret_code:
268 rc = ctypes.c_byte(sipp_ret_code).value
269 raise TestError("SIPp returned error " + str(rc) + ": " + sipp_err_to_str(rc))
270
271
272# Here where it all comes together
273test = TestParam(SIPP_SCEN_XML[:-4],
274 PJSUA_INST_PARAM,
275 TEST_FUNC)