run.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. import sys
  2. import re
  3. import os
  4. import subprocess
  5. import random
  6. import telnetlib
  7. import time
  8. import threading
  9. import traceback
  10. import getopt
  11. import inc_cfg as inc
  12. import inc_const as const
  13. import inc_util as util
  14. # Vars
  15. G_EXE = "" # pjsua executable path
  16. G_INUNIX = False # flags that test is running in Unix
  17. # Usage string
  18. usage = \
  19. """
  20. run.py - Automated test driver
  21. Usage:
  22. run.py [options] MODULE CONFIG
  23. Options:
  24. --exe, -e pjsua executable path
  25. --null-audio, -n use null audio
  26. Sample:
  27. run.py -n mod_run.py scripts-run/100_simple.py
  28. """
  29. # Parse arguments
  30. try:
  31. opts, args = getopt.getopt(sys.argv[1:], "hne:", ["help", "null-audio", "exe="])
  32. except getopt.GetoptError as err:
  33. print(str(err))
  34. print(usage)
  35. sys.exit(2)
  36. for o, a in opts:
  37. if o in ("-h", "--help"):
  38. print(usage)
  39. sys.exit()
  40. elif o in ("-n", "--null-audio"):
  41. inc.HAS_SND_DEV = 0
  42. elif o in ("-e", "--exe"):
  43. G_EXE = a
  44. else:
  45. print("Unknown options")
  46. sys.exit(2)
  47. if len(args) != 2:
  48. print("Invalid arguments")
  49. print(usage)
  50. sys.exit(2)
  51. # Set global ARGS to be used by modules
  52. inc.ARGS = args
  53. # Get the pjsua executable name
  54. if G_EXE == "":
  55. if sys.platform.find("win32")!=-1:
  56. EXE_DIR = "../../pjsip-apps/bin/"
  57. EXECUTABLES = [ "pjsua_vc6d.exe",
  58. "pjsua_vc6.exe",
  59. "pjsua-i386-Win32-vc8-Debug.exe",
  60. "pjsua-i386-Win32-vc8-Debug-Dynamic.exe",
  61. "pjsua-i386-Win32-vc8-Debug-Static.exe",
  62. "pjsua-i386-Win32-vc8-Release.exe",
  63. "pjsua-i386-Win32-vc8-Release-Dynamic.exe",
  64. "pjsua-i386-Win32-vc8-Release-Static.exe",
  65. "pjsua-i386-Win32-vc14-Debug.exe",
  66. "pjsua-i386-Win32-vc14-Debug-Dynamic.exe",
  67. "pjsua-i386-Win32-vc14-Debug-Static.exe",
  68. "pjsua-i386-Win32-vc14-Release.exe",
  69. "pjsua-i386-Win32-vc14-Release-Dynamic.exe",
  70. "pjsua-i386-Win32-vc14-Release-Static.exe"
  71. ]
  72. e_ts = 0
  73. for e in EXECUTABLES:
  74. e = EXE_DIR + e
  75. if os.access(e, os.F_OK):
  76. st = os.stat(e)
  77. if e_ts==0 or e_ts<st.st_mtime:
  78. G_EXE = e
  79. e_ts = st.st_mtime
  80. if G_EXE=="":
  81. print("Unable to find valid pjsua. Please build pjsip first")
  82. sys.exit(1)
  83. G_INUNIX = False
  84. else:
  85. f = open("../../build.mak", "r")
  86. while True:
  87. line = f.readline()
  88. if not line:
  89. break
  90. if line.find("TARGET_NAME")!=-1:
  91. print(line)
  92. G_EXE="../../pjsip-apps/bin/pjsua-" + line.split(":= ")[1]
  93. break
  94. if G_EXE=="":
  95. print("Unable to find ../../../build.mak. Please build pjsip first")
  96. sys.exit(1)
  97. G_INUNIX = True
  98. else:
  99. if sys.platform.lower().find("win32")!=-1 or sys.platform.lower().find("microsoft")!=-1:
  100. G_INUNIX = False
  101. else:
  102. G_INUNIX = True
  103. G_EXE = G_EXE.rstrip("\n\r \t")
  104. ###################################
  105. # Poor man's 'expect'-like class
  106. class Expect(threading.Thread):
  107. proc = None
  108. telnet = None
  109. use_telnet = False
  110. echo = False
  111. trace_enabled = False
  112. inst_param = None
  113. rh = re.compile(const.DESTROYED)
  114. ra = re.compile(const.ASSERT, re.I)
  115. rr = re.compile(const.STDOUT_REFRESH)
  116. t0 = time.time()
  117. output = ""
  118. lock = threading.Lock()
  119. running = False
  120. def __init__(self, inst_param):
  121. threading.Thread.__init__(self)
  122. self.inst_param = inst_param
  123. self.name = inst_param.name
  124. self.echo = inst_param.echo_enabled
  125. self.trace_enabled = inst_param.trace_enabled
  126. self.use_telnet = inst_param.telnet_enabled
  127. self.telnet = None
  128. def run(self):
  129. if self.use_telnet:
  130. fullcmd = G_EXE + " " + self.inst_param.arg + " --use-cli --no-cli-console --cli-telnet-port=%d" % (self.inst_param.telnet_port)
  131. self.trace("Popen " + fullcmd)
  132. self.proc = subprocess.Popen(fullcmd, shell=G_INUNIX)
  133. # start telnet-ing to pjsua, raise exception if telnet fails after 5s
  134. t0 = time.time()
  135. while self.proc.poll() is None and self.telnet is None:
  136. try:
  137. time.sleep(0.01)
  138. self.telnet = telnetlib.Telnet('127.0.0.1', port=self.inst_param.telnet_port, timeout=60)
  139. except Exception as e:
  140. t1 = time.time()
  141. dur = int(t1 - t0)
  142. if dur > 5:
  143. raise inc.TestError(self.name + ": Timeout connecting to pjsua: " + repr(e))
  144. self.running = True
  145. while self.proc.poll() is None:
  146. line = self.telnet.read_until(b'\n', 60)
  147. linestr = line.decode('utf-8')
  148. if linestr == "" or const.DESTROYED in linestr:
  149. break;
  150. #Print the line if echo is ON
  151. if self.echo:
  152. try:
  153. print(self.name + ": " + linestr.rstrip())
  154. except UnicodeEncodeError:
  155. print((self.name + ": " + linestr.rstrip()).encode('utf-8'))
  156. self.lock.acquire()
  157. self.output += linestr
  158. self.lock.release()
  159. self.running = False
  160. else:
  161. fullcmd = G_EXE + " " + self.inst_param.arg + " --stdout-refresh=5 --stdout-refresh-text=" + const.STDOUT_REFRESH
  162. if not self.inst_param.enable_buffer:
  163. fullcmd = fullcmd + " --stdout-no-buf"
  164. self.trace("Popen " + fullcmd)
  165. self.proc = subprocess.Popen(fullcmd, shell=G_INUNIX, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=False)
  166. self.running = True
  167. while self.proc.poll() is None:
  168. line = self.proc.stdout.readline()
  169. if line == "":
  170. break;
  171. #Print the line if echo is ON
  172. if self.echo:
  173. print(self.name + ": " + line.rstrip())
  174. self.lock.acquire()
  175. self.output += line
  176. self.lock.release()
  177. self.running = False
  178. def send(self, cmd):
  179. self.trace("send " + cmd)
  180. if self.use_telnet:
  181. self.telnet.write((cmd + '\r\n').encode('utf-8'))
  182. else:
  183. self.proc.stdin.writelines(cmd + "\n")
  184. self.proc.stdin.flush()
  185. def expect(self, pattern, raise_on_error=True, title="", timeout=15):
  186. # no prompt for telnet
  187. if self.use_telnet and pattern==const.PROMPT:
  188. return
  189. self.trace("expect " + pattern)
  190. r = re.compile(pattern, re.I)
  191. found_at = -1
  192. t0 = time.time()
  193. while found_at < 0:
  194. self.lock.acquire()
  195. lines = self.output.splitlines()
  196. for i, line in enumerate(lines):
  197. # Search for expected text
  198. if r.search(line) != None:
  199. found_at = i
  200. break
  201. # Trap assertion error
  202. if raise_on_error:
  203. if self.ra.search(line) != None:
  204. self.lock.release()
  205. raise inc.TestError(self.name + ": " + line)
  206. self.output = '\n'.join(lines[found_at+1:])+"\n" if found_at >= 0 else ""
  207. self.lock.release()
  208. if found_at >= 0:
  209. return line
  210. if not self.running:
  211. if raise_on_error:
  212. raise inc.TestError(self.name + ": Premature EOF")
  213. break
  214. else:
  215. t1 = time.time()
  216. dur = int(t1 - t0)
  217. if dur > timeout:
  218. self.trace("Timed-out!")
  219. if raise_on_error:
  220. raise inc.TestError(self.name + " " + title + ": Timeout expecting pattern: \"" + pattern + "\"")
  221. break
  222. else:
  223. time.sleep(0.01)
  224. return None
  225. def get_config(self, key_config):
  226. self.send("dd")
  227. line = self.expect(key_config);
  228. return line
  229. def sync_stdout(self):
  230. if not self.use_telnet:
  231. self.trace("sync_stdout")
  232. cmd = "echo 1" + str(random.randint(1000,9999))
  233. self.send(cmd)
  234. self.expect(cmd)
  235. def wait(self):
  236. self.trace("wait")
  237. self.join()
  238. self.proc.communicate()
  239. if self.telnet:
  240. self.telnet.close()
  241. def trace(self, s):
  242. if self.trace_enabled:
  243. now = time.time()
  244. fmt = self.name + ": " + "================== " + s + " ==================" + " [at t=%(time)03d]"
  245. try:
  246. print(fmt % {'time':int(now - self.t0)})
  247. except UnicodeEncodeError:
  248. print((fmt % {'time':int(now - self.t0)}).encode('utf-8'))
  249. #########################
  250. # Error handling
  251. def handle_error(errmsg, t, close_processes = True):
  252. print("====== Caught error: " + errmsg + " ======")
  253. if (close_processes):
  254. time.sleep(1)
  255. for p in t.process:
  256. # Protect against 'Broken pipe' exception
  257. try:
  258. if not p.use_telnet:
  259. p.send("q")
  260. p.send("q")
  261. else:
  262. p.send("shutdown")
  263. except:
  264. pass
  265. is_err = False
  266. try:
  267. ret = p.expect(const.DESTROYED, False)
  268. if ret is None:
  269. is_err = True
  270. except:
  271. is_err = True
  272. if is_err and p.proc.poll() is None:
  273. if sys.hexversion >= 0x02060000:
  274. p.proc.terminate()
  275. else:
  276. p.wait()
  277. else:
  278. p.wait()
  279. print("Test completed with error: " + errmsg)
  280. sys.exit(1)
  281. #########################
  282. # MAIN
  283. # Import the test script
  284. script = util.load_module_from_file("script", inc.ARGS[0])
  285. # Init random seed
  286. random.seed()
  287. # Validate
  288. if script.test == None:
  289. print("Error: no test defined")
  290. sys.exit(1)
  291. if script.test.skip:
  292. print("Test " + script.test.title + " is skipped")
  293. sys.exit(0)
  294. if len(script.test.inst_params) == 0:
  295. print("Error: test doesn't contain pjsua run descriptions")
  296. sys.exit(1)
  297. # Instantiate pjsuas
  298. print("====== Running " + script.test.title + " ======")
  299. print("Using " + G_EXE + " as pjsua executable")
  300. for inst_param in script.test.inst_params:
  301. retry = 0
  302. process_running = False
  303. while (not process_running) and retry < 3:
  304. p = None
  305. retry += 1
  306. try:
  307. # Create pjsua's Expect instance from the param
  308. p = Expect(inst_param)
  309. p.start()
  310. except inc.TestError as e:
  311. handle_error(e.desc, script.test)
  312. # wait process ready
  313. if not p.use_telnet:
  314. while True:
  315. try:
  316. p.send("echo 1")
  317. except:
  318. time.sleep(0.1)
  319. continue
  320. break
  321. process_running = True
  322. else:
  323. t0 = time.time()
  324. while p.telnet is None:
  325. time.sleep(0.1)
  326. dur = int(time.time() - t0)
  327. if dur > 5:
  328. break
  329. process_running = p.telnet is not None
  330. # wait before retrying
  331. if not process_running and retry < 2:
  332. time.sleep(2)
  333. if not process_running:
  334. handle_error("Failed running pjsua", script.test)
  335. # add running instance
  336. script.test.process.append(p)
  337. for p in script.test.process:
  338. try:
  339. # Wait until registration completes
  340. if p.inst_param.have_reg:
  341. p.send("rr")
  342. p.expect(p.inst_param.uri+".*registration success")
  343. # Synchronize stdout
  344. if not p.use_telnet:
  345. p.send("")
  346. p.expect(const.PROMPT)
  347. p.send("echo 1")
  348. p.expect("echo 1")
  349. except inc.TestError as e:
  350. handle_error(e.desc, script.test)
  351. # Run the test function
  352. if script.test.test_func != None:
  353. try:
  354. script.test.test_func(script.test)
  355. except inc.TestError as e:
  356. handle_error(e.desc, script.test)
  357. except:
  358. handle_error("Unknown error: " + str(traceback.format_exc()), script.test)
  359. # Shutdown all instances
  360. for p in script.test.process:
  361. # Unregister if we have_reg to make sure that next tests
  362. # won't fail
  363. if p.inst_param.have_reg:
  364. p.send("ru")
  365. p.expect(p.inst_param.uri+".*unregistration success")
  366. if p.use_telnet:
  367. p.send("shutdown")
  368. else:
  369. p.send("q")
  370. time.sleep(0.5)
  371. for p in script.test.process:
  372. if p.running:
  373. p.expect(const.DESTROYED, False)
  374. p.wait()
  375. # Run the post test function
  376. if script.test.post_func != None:
  377. try:
  378. script.test.post_func(script.test)
  379. except inc.TestError as e:
  380. handle_error(e.desc, script.test, False)
  381. # Done
  382. print("Test " + script.test.title + " completed successfully")
  383. sys.exit(0)