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