run-tests.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. #!/usr/bin/python
  2. #
  3. # AP tests
  4. # Copyright (c) 2013, Jouni Malinen <j@w1.fi>
  5. #
  6. # This software may be distributed under the terms of the BSD license.
  7. # See README for more details.
  8. import os
  9. import re
  10. import sys
  11. import time
  12. from datetime import datetime
  13. import argparse
  14. import subprocess
  15. import logging
  16. logger = logging.getLogger()
  17. sys.path.append('../../wpaspy')
  18. from wpasupplicant import WpaSupplicant
  19. from hostapd import HostapdGlobal
  20. def reset_devs(dev, apdev):
  21. hapd = HostapdGlobal()
  22. for d in dev:
  23. try:
  24. d.reset()
  25. except Exception, e:
  26. logger.info("Failed to reset device " + d.ifname)
  27. print str(e)
  28. for ap in apdev:
  29. hapd.remove(ap['ifname'])
  30. def report(conn, build, commit, run, test, result, diff):
  31. if conn:
  32. if not build:
  33. build = ''
  34. if not commit:
  35. commit = ''
  36. sql = "INSERT INTO results(test,result,run,time,duration,build,commitid) VALUES(?, ?, ?, ?, ?, ?, ?)"
  37. params = (test, result, run, time.time(), diff.total_seconds(), build, commit)
  38. try:
  39. conn.execute(sql, params)
  40. conn.commit()
  41. except Exception, e:
  42. print "sqlite: " + str(e)
  43. print "sql: %r" % (params, )
  44. class DataCollector(object):
  45. def __init__(self, logdir, testname, tracing, dmesg):
  46. self._logdir = logdir
  47. self._testname = testname
  48. self._tracing = tracing
  49. self._dmesg = dmesg
  50. def __enter__(self):
  51. if self._tracing:
  52. output = os.path.join(self._logdir, '%s.dat' % (self._testname, ))
  53. self._trace_cmd = subprocess.Popen(['sudo', 'trace-cmd', 'record', '-o', output, '-e', 'mac80211', '-e', 'cfg80211', 'sh', '-c', 'echo STARTED ; read l'],
  54. stdin=subprocess.PIPE,
  55. stdout=subprocess.PIPE,
  56. stderr=open('/dev/null', 'w'),
  57. cwd=self._logdir)
  58. l = self._trace_cmd.stdout.read(7)
  59. while not 'STARTED' in l:
  60. l += self._trace_cmd.stdout.read(1)
  61. def __exit__(self, type, value, traceback):
  62. if self._tracing:
  63. self._trace_cmd.stdin.write('DONE\n')
  64. self._trace_cmd.wait()
  65. if self._dmesg:
  66. output = os.path.join(self._logdir, '%s.dmesg' % (self._testname, ))
  67. subprocess.call(['sudo', 'dmesg', '-c'], stdout=open(output, 'w'))
  68. def main():
  69. tests = []
  70. test_modules = []
  71. for t in os.listdir("."):
  72. m = re.match(r'(test_.*)\.py$', t)
  73. if m:
  74. logger.debug("Import test cases from " + t)
  75. mod = __import__(m.group(1))
  76. test_modules.append(mod.__name__.replace('test_', '', 1))
  77. for s in dir(mod):
  78. if s.startswith("test_"):
  79. func = mod.__dict__.get(s)
  80. tests.append(func)
  81. test_names = list(set([t.__name__.replace('test_', '', 1) for t in tests]))
  82. run = None
  83. print_res = False
  84. parser = argparse.ArgumentParser(description='hwsim test runner')
  85. parser.add_argument('--logdir', metavar='<directory>',
  86. help='log output directory for all other options, ' +
  87. 'must be given if other log options are used')
  88. group = parser.add_mutually_exclusive_group()
  89. group.add_argument('-d', const=logging.DEBUG, action='store_const',
  90. dest='loglevel', default=logging.INFO,
  91. help="verbose debug output")
  92. group.add_argument('-q', const=logging.WARNING, action='store_const',
  93. dest='loglevel', help="be quiet")
  94. group.add_argument('-l', action='store_true', dest='logfile',
  95. help='store debug log to a file (in log directory)')
  96. parser.add_argument('-e', metavar="<filename>", dest='errorfile',
  97. nargs='?', const="failed",
  98. help='error filename (in log directory)')
  99. parser.add_argument('-S', metavar='<sqlite3 db>', dest='database',
  100. help='database to write results to')
  101. parser.add_argument('--commit', metavar='<commit id>',
  102. help='commit ID, only for database')
  103. parser.add_argument('-b', metavar='<build>', dest='build', help='build ID')
  104. parser.add_argument('-L', action='store_true', dest='update_tests_db',
  105. help='List tests (and update descriptions in DB)')
  106. parser.add_argument('-T', action='store_true', dest='tracing',
  107. help='collect tracing per test case (in log directory)')
  108. parser.add_argument('-D', action='store_true', dest='dmesg',
  109. help='collect dmesg per test case (in log directory)')
  110. parser.add_argument('-f', dest='testmodules', metavar='<test module>',
  111. help='execute only tests from these test modules',
  112. type=str, choices=[[]] + test_modules, nargs='+')
  113. parser.add_argument('tests', metavar='<test>', nargs='*', type=str,
  114. help='tests to run (only valid without -f)',
  115. choices=[[]] + test_names)
  116. args = parser.parse_args()
  117. if args.tests and args.testmodules:
  118. print 'Invalid arguments - both test module and tests given'
  119. sys.exit(2)
  120. if not args.logdir:
  121. if os.path.exists('logs/current'):
  122. args.logdir = 'logs/current'
  123. else:
  124. args.logdir = 'logs'
  125. if args.logfile:
  126. logger.setLevel(logging.DEBUG)
  127. file_name = os.path.join(args.logdir, 'run-tests.log')
  128. log_handler = logging.FileHandler(file_name)
  129. fmt = "%(asctime)s %(levelname)s %(message)s"
  130. log_formatter = logging.Formatter(fmt)
  131. log_handler.setFormatter(log_formatter)
  132. logger.addHandler(log_handler)
  133. log_to_file = True
  134. else:
  135. logging.basicConfig(level=args.loglevel)
  136. log_handler = None
  137. log_to_file = False
  138. if args.loglevel == logging.WARNING:
  139. print_res = True
  140. error_file = args.errorfile and os.path.join(args.logdir, args.errorfile)
  141. if args.database:
  142. import sqlite3
  143. conn = sqlite3.connect(args.database)
  144. else:
  145. conn = None
  146. if conn:
  147. run = int(time.time())
  148. if args.update_tests_db:
  149. for t in tests:
  150. name = t.__name__.replace('test_', '', 1)
  151. print name + " - " + t.__doc__
  152. if conn:
  153. sql = 'INSERT OR REPLACE INTO tests(test,description) VALUES (?, ?)'
  154. params = (name, t.__doc__)
  155. try:
  156. conn.execute(sql, params)
  157. except Exception, e:
  158. print "sqlite: " + str(e)
  159. print "sql: %r" % (params,)
  160. if conn:
  161. conn.commit()
  162. conn.close()
  163. sys.exit(0)
  164. dev0 = WpaSupplicant('wlan0', '/tmp/wpas-wlan0')
  165. dev1 = WpaSupplicant('wlan1', '/tmp/wpas-wlan1')
  166. dev2 = WpaSupplicant('wlan2', '/tmp/wpas-wlan2')
  167. dev = [ dev0, dev1, dev2 ]
  168. apdev = [ ]
  169. apdev.append({"ifname": 'wlan3', "bssid": "02:00:00:00:03:00"})
  170. apdev.append({"ifname": 'wlan4', "bssid": "02:00:00:00:04:00"})
  171. for d in dev:
  172. if not d.ping():
  173. logger.info(d.ifname + ": No response from wpa_supplicant")
  174. return
  175. logger.info("DEV: " + d.ifname + ": " + d.p2p_dev_addr())
  176. for ap in apdev:
  177. logger.info("APDEV: " + ap['ifname'])
  178. passed = []
  179. skipped = []
  180. failed = []
  181. # make sure nothing is left over from previous runs
  182. # (if there were any other manual runs or we crashed)
  183. reset_devs(dev, apdev)
  184. if args.dmesg:
  185. subprocess.call(['sudo', 'dmesg', '-c'], stdout=open('/dev/null', 'w'))
  186. for t in tests:
  187. name = t.__name__.replace('test_', '', 1)
  188. if args.tests:
  189. if not name in args.tests:
  190. continue
  191. if args.testmodules:
  192. if not t.__module__.replace('test_', '', 1) in args.testmodules:
  193. continue
  194. if log_handler:
  195. log_handler.stream.close()
  196. logger.removeHandler(log_handler)
  197. file_name = os.path.join(args.logdir, name + '.log')
  198. log_handler = logging.FileHandler(file_name)
  199. log_handler.setFormatter(log_formatter)
  200. logger.addHandler(log_handler)
  201. with DataCollector(args.logdir, name, args.tracing, args.dmesg):
  202. logger.info("START " + name)
  203. if log_to_file:
  204. print "START " + name
  205. sys.stdout.flush()
  206. if t.__doc__:
  207. logger.info("Test: " + t.__doc__)
  208. start = datetime.now()
  209. for d in dev:
  210. try:
  211. d.request("NOTE TEST-START " + name)
  212. except Exception, e:
  213. logger.info("Failed to issue TEST-START before " + name + " for " + d.ifname)
  214. logger.info(e)
  215. print "FAIL " + name + " - could not start test"
  216. if conn:
  217. conn.close()
  218. conn = None
  219. sys.exit(1)
  220. try:
  221. if t.func_code.co_argcount > 1:
  222. res = t(dev, apdev)
  223. else:
  224. res = t(dev)
  225. end = datetime.now()
  226. diff = end - start
  227. if res == "skip":
  228. skipped.append(name)
  229. result = "SKIP"
  230. else:
  231. passed.append(name)
  232. result = "PASS"
  233. report(conn, args.build, args.commit, run, name, result, diff)
  234. result = result + " " + name + " "
  235. result = result + str(diff.total_seconds()) + " " + str(end)
  236. logger.info(result)
  237. if log_to_file or print_res:
  238. print result
  239. sys.stdout.flush()
  240. except Exception, e:
  241. end = datetime.now()
  242. diff = end - start
  243. logger.info(e)
  244. failed.append(name)
  245. report(conn, args.build, args.commit, run, name, "FAIL", diff)
  246. result = "FAIL " + name + " " + str(diff.total_seconds()) + " " + str(end)
  247. logger.info(result)
  248. if log_to_file:
  249. print result
  250. sys.stdout.flush()
  251. for d in dev:
  252. try:
  253. d.request("NOTE TEST-STOP " + name)
  254. except Exception, e:
  255. logger.info("Failed to issue TEST-STOP after " + name + " for " + d.ifname)
  256. logger.info(e)
  257. reset_devs(dev, apdev)
  258. for i in range(0, 3):
  259. try:
  260. import getpass
  261. srcname = os.path.join(args.logdir, 'log' + str(i))
  262. dstname = os.path.join(args.logdir, name + '.log' + str(i))
  263. num = 0
  264. while os.path.exists(dstname):
  265. dstname = os.path.join(args.logdir, name + '.log' + str(i) + '-' + str(num))
  266. num = num + 1
  267. os.rename(srcname, dstname)
  268. dev[i].request("RELOG")
  269. subprocess.call(['sudo', 'chown', '-f', getpass.getuser(),
  270. srcname])
  271. except Exception, e:
  272. logger.info("Failed to rename log files")
  273. logger.info(e)
  274. if log_handler:
  275. log_handler.stream.close()
  276. logger.removeHandler(log_handler)
  277. file_name = os.path.join(args.logdir, 'run-tests.log')
  278. log_handler = logging.FileHandler(file_name)
  279. log_handler.setFormatter(log_formatter)
  280. logger.addHandler(log_handler)
  281. if conn:
  282. conn.close()
  283. if len(failed):
  284. logger.info("passed " + str(len(passed)) + " test case(s)")
  285. logger.info("skipped " + str(len(skipped)) + " test case(s)")
  286. logger.info("failed tests: " + str(failed))
  287. if error_file:
  288. f = open(error_file, 'w')
  289. f.write(str(failed) + '\n')
  290. f.close()
  291. sys.exit(1)
  292. logger.info("passed all " + str(len(passed)) + " test case(s)")
  293. if len(skipped):
  294. logger.info("skipped " + str(len(skipped)) + " test case(s)")
  295. if log_to_file:
  296. print "passed all " + str(len(passed)) + " test case(s)"
  297. if len(skipped):
  298. print "skipped " + str(len(skipped)) + " test case(s)"
  299. if __name__ == "__main__":
  300. main()