From a37a581a5e1f22027081f41891f2bf9ca88d5998 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Mon, 13 Apr 2015 12:28:02 +0000 Subject: Re #1842: - 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: http://svn.pjsip.org/repos/pjproject/trunk@5067 74dad513-b988-da41-8d7b-12977e46ad98 --- tests/pjsua/mod_sipp.py | 8 +- tests/pjsua/run.py | 123 +++++++++++++++------ .../scripts-sipp/uas-reinv-with-less-media.py | 4 +- 3 files changed, 98 insertions(+), 37 deletions(-) (limited to 'tests/pjsua') diff --git a/tests/pjsua/mod_sipp.py b/tests/pjsua/mod_sipp.py index c705c966..25081086 100644 --- a/tests/pjsua/mod_sipp.py +++ b/tests/pjsua/mod_sipp.py @@ -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 diff --git a/tests/pjsua/run.py b/tests/pjsua/run.py index 7eee316a..78e1b214 100644 --- a/tests/pjsua/run.py +++ b/tests/pjsua/run.py @@ -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 diff --git a/tests/pjsua/scripts-sipp/uas-reinv-with-less-media.py b/tests/pjsua/scripts-sipp/uas-reinv-with-less-media.py index 42b2b881..83ba3997 100644 --- a/tests/pjsua/scripts-sipp/uas-reinv-with-less-media.py +++ b/tests/pjsua/scripts-sipp/uas-reinv-with-less-media.py @@ -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"] + ] -- cgit v1.2.3