- Updated python test driver run.py to perform stdout polling using a dedicated thread, this will increase the robustness of pattern matcing class "Expect" and remove the possibility of stucked pjsua (due to output buffer full when no stdout read polling is done).
 - Also updated other test driver and scenario accordingly.


git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@5067 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Nanang Izzuddin 2015-04-13 12:28:02 +00:00
parent 66f7e5611b
commit 8d6ca24530
3 changed files with 98 additions and 37 deletions

View File

@ -40,7 +40,7 @@ else:
FDEVNULL = None
# SIPp executable path and param
#SIPP_PATH = '"C:\\Program Files (x86)\\Sipp_3.2\\sipp.exe"'
#SIPP_PATH = '"C:\\devs\\bin\\Sipp_3.2\\sipp.exe"'
SIPP_PATH = 'sipp'
SIPP_PORT = 6000
SIPP_PARAM = "-m 1 -i 127.0.0.1 -p " + str(SIPP_PORT)
@ -224,8 +224,10 @@ def exec_pjsua_expects(t, sipp):
# PJSUA process may stuck.
# Ideally the poll should be done contiunously until SIPp process is
# terminated.
for ua_idx in range(len(ua)):
ua[ua_idx].expect(inc_const.STDOUT_REFRESH, raise_on_error = False)
# Update: now pjsua stdout is polled continuously by a dedicated thread,
# so the poll is no longer needed
#for ua_idx in range(len(ua)):
# ua[ua_idx].expect(inc_const.STDOUT_REFRESH, raise_on_error = False)
return ua_err_st

View File

@ -6,6 +6,8 @@ import os
import subprocess
import random
import time
import threading
import traceback
import getopt
import inc_const as const
@ -109,59 +111,94 @@ G_EXE = G_EXE.rstrip("\n\r \t")
###################################
# Poor man's 'expect'-like class
class Expect:
class Expect(threading.Thread):
proc = None
echo = False
trace_enabled = False
name = ""
inst_param = None
rh = re.compile(const.DESTROYED)
ra = re.compile(const.ASSERT, re.I)
rr = re.compile(const.STDOUT_REFRESH)
t0 = time.time()
output = ""
lock = threading.Lock()
running = False
def __init__(self, inst_param):
threading.Thread.__init__(self)
self.inst_param = inst_param
self.name = inst_param.name
self.echo = inst_param.echo_enabled
self.trace_enabled = inst_param.trace_enabled
def run(self):
fullcmd = G_EXE + " " + inst_param.arg + " --stdout-refresh=5 --stdout-refresh-text=" + const.STDOUT_REFRESH
if not inst_param.enable_buffer:
fullcmd = fullcmd + " --stdout-no-buf"
self.trace("Popen " + fullcmd)
self.proc = subprocess.Popen(fullcmd, shell=G_INUNIX, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=False)
self.running = True
while self.proc.poll() == None:
line = self.proc.stdout.readline()
if line == "":
break;
#Print the line if echo is ON
if self.echo:
print self.name + ": " + line.rstrip()
self.lock.acquire()
self.output += line
self.lock.release()
self.running = False
def send(self, cmd):
self.trace("send " + cmd)
self.proc.stdin.writelines(cmd + "\n")
self.proc.stdin.flush()
def expect(self, pattern, raise_on_error=True, title=""):
self.trace("expect " + pattern)
r = re.compile(pattern, re.I)
refresh_cnt = 0
while True:
line = self.proc.stdout.readline()
if line == "":
raise inc.TestError(self.name + ": Premature EOF")
# Print the line if echo is ON
if self.echo:
print self.name + ": " + line.rstrip()
# Trap assertion error
if self.ra.search(line) != None:
found_at = -1
t0 = time.time()
while found_at < 0:
self.lock.acquire()
lines = self.output.splitlines()
for i, line in enumerate(lines):
# Search for expected text
if r.search(line) != None:
found_at = i
break
# Trap assertion error
if raise_on_error:
raise inc.TestError(self.name + ": " + line)
else:
return None
# Count stdout refresh text.
if self.rr.search(line) != None:
refresh_cnt = refresh_cnt+1
if refresh_cnt >= 6:
if self.ra.search(line) != None:
self.lock.release()
raise inc.TestError(self.name + ": " + line)
self.output = '\n'.join(lines[found_at+1:]) if found_at >= 0 else ""
self.lock.release()
if found_at >= 0:
return line
if not self.running:
if raise_on_error:
raise inc.TestError(self.name + ": Premature EOF")
break
else:
t1 = time.time()
dur = int(t1 - t0)
if dur > 15:
self.trace("Timed-out!")
if raise_on_error:
raise inc.TestError(self.name + " " + title + ": Timeout expecting pattern: \"" + pattern + "\"")
else:
return None # timeout
# Search for expected text
if r.search(line) != None:
return line
break
else:
time.sleep(0.01)
return None
def sync_stdout(self):
self.trace("sync_stdout")
@ -171,6 +208,7 @@ class Expect:
def wait(self):
self.trace("wait")
self.join()
self.proc.communicate()
def trace(self, s):
@ -206,6 +244,7 @@ def handle_error(errmsg, t, close_processes = True):
p.wait()
else:
p.wait()
print "Test completed with error: " + errmsg
sys.exit(1)
@ -240,17 +279,32 @@ for inst_param in script.test.inst_params:
try:
# Create pjsua's Expect instance from the param
p = Expect(inst_param)
p.start()
except inc.TestError, e:
handle_error(e.desc, script.test)
# wait process ready
while True:
try:
p.send("echo 1")
except:
time.sleep(0.1)
continue
break
# add running instance
script.test.process.append(p)
for p in script.test.process:
try:
# Wait until registration completes
if inst_param.have_reg:
p.expect(inst_param.uri+".*registration success")
if p.inst_param.have_reg:
p.expect(p.inst_param.uri+".*registration success")
# Synchronize stdout
p.send("")
p.expect(const.PROMPT)
p.send("echo 1")
p.send("echo 1")
p.expect("echo 1")
# add running instance
script.test.process.append(p)
except inc.TestError, e:
handle_error(e.desc, script.test)
@ -261,9 +315,10 @@ if script.test.test_func != None:
script.test.test_func(script.test)
except inc.TestError, e:
handle_error(e.desc, script.test)
except:
handle_error("Unknown error: " + str(traceback.format_exc()), script.test)
# Shutdown all instances
time.sleep(2)
for p in script.test.process:
# Unregister if we have_reg to make sure that next tests
# won't wail
@ -271,9 +326,11 @@ for p in script.test.process:
p.send("ru")
p.expect(p.inst_param.uri+".*unregistration success")
p.send("q")
p.send("q")
time.sleep(0.5)
p.expect(const.DESTROYED, False)
time.sleep(0.5)
for p in script.test.process:
if p.running:
p.expect(const.DESTROYED, False)
p.wait()
# Run the post test function

View File

@ -5,4 +5,6 @@ import inc_const as const
PJSUA = ["--null-audio --extra-audio --max-calls=1 $SIPP_URI"]
# Send hold after remote holds (double hold)
PJSUA_EXPECTS = [[0, const.MEDIA_HOLD, "H"]]
PJSUA_EXPECTS = [[0, const.MEDIA_HOLD, ""],
[0, "ACK sip:", "H"]
]