~ubuntu-branches/ubuntu/wily/sflphone/wily

« back to all changes in this revision

Viewing changes to daemon/libs/pjproject-2.1.0/tests/pjsua/mod_sipp.py

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2015-01-07 14:51:16 UTC
  • mfrom: (4.3.5 sid)
  • Revision ID: package-import@ubuntu.com-20150107145116-yxnafinf4lrdvrmx
Tags: 1.4.1-0.1ubuntu1
* Merge with Debian, remaining changes:
 - Drop soprano, nepomuk build-dep
* Drop ubuntu patches, now upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# $Id: mod_sipp.py 4188 2012-06-29 09:01:17Z nanang $
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
 
 
22
 
import ctypes
23
 
import time
24
 
import imp
25
 
import sys
26
 
import os
27
 
import re
28
 
import subprocess
29
 
from inc_cfg import *
30
 
import inc_const
31
 
 
32
 
# flags that test is running in Unix
33
 
G_INUNIX = False
34
 
if sys.platform.lower().find("win32")!=-1 or sys.platform.lower().find("microsoft")!=-1:
35
 
    G_INUNIX = False
36
 
else:
37
 
    G_INUNIX = True
38
 
 
39
 
# /dev/null handle, for redirecting output when SIPP is not in background mode
40
 
FDEVNULL = None
41
 
 
42
 
# SIPp executable path and param
43
 
#SIPP_PATH = '"C:\\Program Files (x86)\\Sipp_3.2\\sipp.exe"'
44
 
SIPP_PATH = 'sipp'
45
 
SIPP_PORT    = 6000
46
 
SIPP_PARAM = "-m 1 -i 127.0.0.1 -p " + str(SIPP_PORT)
47
 
SIPP_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
51
 
SIPP_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)
55
 
PJSUA_INST_PARAM = []
56
 
PJSUA_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
61
 
PJSUA_DEF_PARAM = "--null-audio --max-calls=1 --no-tcp --id=sip:a@localhost --username=a --realm=*"
62
 
 
63
 
# Get SIPp scenario (XML file)
64
 
SIPP_SCEN_XML  = ""
65
 
if ARGS[1].endswith('.xml'):
66
 
    SIPP_SCEN_XML  = ARGS[1]
67
 
else:
68
 
    exit(-99)
69
 
 
70
 
 
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)
74
 
 
75
 
def resolve_pjsua_uri(mo):
76
 
    return PJSUA_INST_PARAM[int(mo.group(1))].uri[1:-1]
77
 
 
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)
83
 
    return st
84
 
 
85
 
 
86
 
# Init test driver
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
94
 
else:
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
106
 
def 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
162
 
def 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
193
 
def 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
 
 
233
 
def 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
251
 
def 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
273
 
test = TestParam(SIPP_SCEN_XML[:-4],
274
 
                 PJSUA_INST_PARAM,
275
 
                 TEST_FUNC)