]>
git.ipfire.org Git - thirdparty/hostap.git/blob - tests/hwsim/vm/parallel-vm.py
3 # Parallel VM test case executor
4 # Copyright (c) 2014-2015, Jouni Malinen <j@w1.fi>
6 # This software may be distributed under the terms of the BSD license.
7 # See README for more details.
17 logger
= logging
.getLogger()
19 # Test cases that take significantly longer time to execute than average.
20 long_tests
= [ "ap_roam_open",
21 "wpas_mesh_password_mismatch_retry",
22 "wpas_mesh_password_mismatch",
23 "hostapd_oom_wpa2_psk_connect",
24 "ap_hs20_fetch_osu_stop",
27 "nfc_wps_er_handover_pk_hash_mismatch_sta",
28 "go_neg_peers_force_diff_freq",
31 "ap_pmf_sta_unprot_deauth_burst",
32 "ap_bss_add_remove_during_ht_scan",
34 "autoscan_exponential",
37 "ap_inactivity_disconnect",
38 "scan_bss_expiration_age",
40 "discovery_group_client",
46 "ap_ht_40mhz_intolerant_ap",
48 "discovery_pd_retries",
49 "ap_wps_setup_locked_timeout",
53 "grpform_cred_ready_timeout",
54 "hostapd_oom_wpa2_eap_connect",
57 "hostapd_oom_wpa2_eap",
59 "proxyarp_open_ebtables",
61 "obss_scan_40_intolerant",
65 "ap_wps_pbc_timeout" ]
69 for i
in range(num_servers
):
70 failed
+= vm
[i
]['failed']
73 def vm_read_stdout(vm
, i
):
74 global total_started
, total_passed
, total_failed
, total_skipped
79 out
= vm
['proc'].stdout
.read()
82 logger
.debug("VM[%d] stdout.read[%s]" % (i
, out
))
83 pending
= vm
['pending'] + out
86 pos
= pending
.find('\n')
89 line
= pending
[0:pos
].rstrip()
90 pending
= pending
[(pos
+ 1):]
91 logger
.debug("VM[%d] stdout full line[%s]" % (i
, line
))
92 if line
.startswith("READY"):
94 elif line
.startswith("PASS"):
97 elif line
.startswith("FAIL"):
100 name
= line
.split(' ')[1]
101 logger
.debug("VM[%d] test case failed: %s" % (i
, name
))
102 vm
['failed'].append(name
)
103 elif line
.startswith("NOT-FOUND"):
106 logger
.info("VM[%d] test case not found" % i
)
107 elif line
.startswith("SKIP"):
110 elif line
.startswith("START"):
112 vm
['out'] += line
+ '\n'
114 vm
['pending'] = pending
117 def show_progress(scr
):
123 global first_run_failures
124 global total_started
, total_passed
, total_failed
, total_skipped
126 total_tests
= len(tests
)
127 logger
.info("Total tests: %d" % total_tests
)
130 scr
.addstr(0, 0, "Parallel test execution status", curses
.A_BOLD
)
131 for i
in range(0, num_servers
):
132 scr
.addstr(i
+ 1, 0, "VM %d:" % (i
+ 1), curses
.A_BOLD
)
133 scr
.addstr(i
+ 1, 10, "starting VM")
134 scr
.addstr(num_servers
+ 1, 0, "Total:", curses
.A_BOLD
)
135 scr
.addstr(num_servers
+ 1, 20, "TOTAL={} STARTED=0 PASS=0 FAIL=0 SKIP=0".format(total_tests
))
138 completed_first_pass
= False
143 first_running
= False
146 for i
in range(0, num_servers
):
147 if completed_first_pass
:
149 if vm
[i
]['first_run_done']:
151 if not vm
[i
]['proc']:
153 if vm
[i
]['proc'].poll() is not None:
157 log
= '{}/{}.srv.{}/console'.format(dir, timestamp
, i
+ 1)
158 with
open(log
, 'r') as f
:
159 if "Kernel panic" in f
.read():
160 scr
.addstr("kernel panic")
161 logger
.info("VM[%d] kernel panic" % i
)
163 scr
.addstr("unexpected exit")
164 logger
.info("VM[%d] unexpected exit" % i
)
171 err
= vm
[i
]['proc'].stderr
.read()
173 logger
.debug("VM[%d] stderr.read[%s]" % (i
, err
))
177 if vm_read_stdout(vm
[i
], i
):
182 vm
[i
]['first_run_done'] = True
183 scr
.addstr("completed first round")
184 logger
.info("VM[%d] completed first round" % i
)
188 vm
[i
]['proc'].stdin
.write(name
+ '\n')
190 logger
.debug("VM[%d] start test %s" % (i
, name
))
192 if not first_running
and not completed_first_pass
:
193 logger
.info("First round of testing completed")
195 logger
.info("Unexpected test cases remaining from first round: " + str(tests
))
196 raise Exception("Unexpected test cases remaining from first round")
197 completed_first_pass
= True
198 for name
in get_failed(vm
):
200 rerun_tests
.append(name
)
201 first_run_failures
.append(name
)
203 for i
in range(num_servers
):
204 if not completed_first_pass
:
206 if not vm
[i
]['proc']:
208 if vm
[i
]['proc'].poll() is not None:
212 log
= '{}/{}.srv.{}/console'.format(dir, timestamp
, i
+ 1)
213 with
open(log
, 'r') as f
:
214 if "Kernel panic" in f
.read():
215 scr
.addstr("kernel panic")
216 logger
.info("VM[%d] kernel panic" % i
)
218 scr
.addstr("completed run")
219 logger
.info("VM[%d] completed run" % i
)
225 err
= vm
[i
]['proc'].stderr
.read()
227 logger
.debug("VM[%d] stderr.read[%s]" % (i
, err
))
232 if vm
[i
]['first_run_done']:
233 vm
[i
]['first_run_done'] = False
236 ready
= vm_read_stdout(vm
[i
], i
)
242 vm
[i
]['proc'].stdin
.write('\n')
243 scr
.addstr("shutting down")
244 logger
.info("VM[%d] shutting down" % i
)
246 name
= rerun_tests
.pop(0)
247 vm
[i
]['proc'].stdin
.write(name
+ '\n')
248 scr
.addstr(name
+ "(*)")
249 logger
.debug("VM[%d] start test %s (*)" % (i
, name
))
255 scr
.move(num_servers
+ 1, 10)
257 scr
.addstr("{} %".format(int(100.0 * (total_passed
+ total_failed
+ total_skipped
) / total_tests
)))
258 scr
.addstr(num_servers
+ 1, 20, "TOTAL={} STARTED={} PASS={} FAIL={} SKIP={}".format(total_tests
, total_started
, total_passed
, total_failed
, total_skipped
))
259 failed
= get_failed(vm
)
261 scr
.move(num_servers
+ 2, 0)
263 scr
.addstr("Failed test cases: ")
277 scr
.addstr("(RETRY FAILED %d)" % len(rerun_tests
))
280 elif first_run_failures
:
281 scr
.addstr("(RETRY FAILED)")
298 global first_run_failures
299 global total_started
, total_passed
, total_failed
, total_skipped
300 global rerun_failures
307 debug_level
= logging
.INFO
308 rerun_failures
= True
309 timestamp
= int(time
.time())
311 scriptsdir
= os
.path
.dirname(os
.path
.realpath(sys
.argv
[0]))
313 p
= argparse
.ArgumentParser(description
='run multiple testing VMs in parallel')
314 p
.add_argument('num_servers', metavar
='number of VMs', type=int, choices
=range(1, 100),
315 help="number of VMs to start")
316 p
.add_argument('-f', dest
='testmodules', metavar
='<test module>',
317 help='execute only tests from these test modules',
319 p
.add_argument('-1', dest
='no_retry', action
='store_const', const
=True, default
=False,
320 help="don't retry failed tests automatically")
321 p
.add_argument('--debug', dest
='debug', action
='store_const', const
=True, default
=False,
322 help="enable debug logging")
323 p
.add_argument('--codecov', dest
='codecov', action
='store_const', const
=True, default
=False,
324 help="enable code coverage collection")
325 p
.add_argument('--shuffle-tests', dest
='shuffle', action
='store_const', const
=True, default
=False,
326 help="shuffle test cases to randomize order")
327 p
.add_argument('--short', dest
='short', action
='store_const', const
=True,
329 help="only run short-duration test cases")
330 p
.add_argument('--long', dest
='long', action
='store_const', const
=True,
332 help="include long-duration test cases")
333 p
.add_argument('--valgrind', dest
='valgrind', action
='store_const',
334 const
=True, default
=False,
335 help="run tests under valgrind")
336 p
.add_argument('params', nargs
='*')
337 args
= p
.parse_args()
338 num_servers
= args
.num_servers
339 rerun_failures
= not args
.no_retry
341 debug_level
= logging
.DEBUG
344 extra_args
+= [ '--valgrind' ]
346 extra_args
+= [ '--long' ]
348 print "Code coverage - build separate binaries"
349 logdir
= "/tmp/hwsim-test-logs/" + str(timestamp
)
351 subprocess
.check_call([os
.path
.join(scriptsdir
, 'build-codecov.sh'),
353 codecov_args
= ['--codecov_dir', logdir
]
359 first_run_failures
= []
361 cmd
= [ os
.path
.join(os
.path
.dirname(scriptsdir
), 'run-tests.py'), '-L' ]
364 cmd
+= args
.testmodules
365 lst
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
366 for l
in lst
.stdout
.readlines():
367 name
= l
.split(' ')[0]
370 sys
.exit("No test cases selected")
372 dir = '/tmp/hwsim-test-logs'
379 from random
import shuffle
381 elif num_servers
> 2 and len(tests
) > 100:
382 # Move test cases with long duration to the beginning as an
383 # optimization to avoid last part of the test execution running a long
384 # duration test case on a single VM while all other VMs have already
385 # completed their work.
391 tests
= [t
for t
in tests
if t
not in long_tests
]
393 logger
.setLevel(debug_level
)
394 log_handler
= logging
.FileHandler('parallel-vm.log')
395 log_handler
.setLevel(debug_level
)
396 fmt
= "%(asctime)s %(levelname)s %(message)s"
397 log_formatter
= logging
.Formatter(fmt
)
398 log_handler
.setFormatter(log_formatter
)
399 logger
.addHandler(log_handler
)
402 for i
in range(0, num_servers
):
403 print("\rStarting virtual machine {}/{}".format(i
+ 1, num_servers
)),
404 logger
.info("Starting virtual machine {}/{}".format(i
+ 1, num_servers
))
405 cmd
= [os
.path
.join(scriptsdir
, 'vm-run.sh'), '--delay', str(i
),
406 '--timestamp', str(timestamp
),
407 '--ext', 'srv.%d' % (i
+ 1),
408 '-i'] + codecov_args
+ extra_args
410 vm
[i
]['first_run_done'] = False
411 vm
[i
]['proc'] = subprocess
.Popen(cmd
,
412 stdin
=subprocess
.PIPE
,
413 stdout
=subprocess
.PIPE
,
414 stderr
=subprocess
.PIPE
)
416 vm
[i
]['pending'] = ""
419 for stream
in [ vm
[i
]['proc'].stdout
, vm
[i
]['proc'].stderr
]:
421 fl
= fcntl
.fcntl(fd
, fcntl
.F_GETFL
)
422 fcntl
.fcntl(fd
, fcntl
.F_SETFL
, fl | os
.O_NONBLOCK
)
425 curses
.wrapper(show_progress
)
427 with
open('{}/{}-parallel.log'.format(dir, timestamp
), 'w') as f
:
428 for i
in range(0, num_servers
):
429 f
.write('VM {}\n{}\n{}\n'.format(i
, vm
[i
]['out'], vm
[i
]['err']))
431 failed
= get_failed(vm
)
433 if first_run_failures
:
434 print "Failed test cases:"
435 for f
in first_run_failures
:
437 logger
.info("Failed: " + f
)
441 double_failed
.append(name
)
442 for test
in first_run_failures
:
443 double_failed
.remove(test
)
444 if not rerun_failures
:
446 elif failed
and not double_failed
:
447 print "All failed cases passed on retry"
448 logger
.info("All failed cases passed on retry")
450 print "Failed even on retry:"
451 for f
in double_failed
:
453 logger
.info("Failed on retry: " + f
)
455 res
= "TOTAL={} PASS={} FAIL={} SKIP={}".format(total_started
,
461 print "Logs: " + dir + '/' + str(timestamp
)
462 logger
.info("Logs: " + dir + '/' + str(timestamp
))
464 for i
in range(0, num_servers
):
465 if len(vm
[i
]['pending']) > 0:
466 logger
.info("Unprocessed stdout from VM[%d]: '%s'" %
467 (i
, vm
[i
]['pending']))
468 log
= '{}/{}.srv.{}/console'.format(dir, timestamp
, i
+ 1)
469 with
open(log
, 'r') as f
:
470 if "Kernel panic" in f
.read():
471 print "Kernel panic in " + log
472 logger
.info("Kernel panic in " + log
)
475 print "Code coverage - preparing report"
476 for i
in range(num_servers
):
477 subprocess
.check_call([os
.path
.join(scriptsdir
,
478 'process-codecov.sh'),
479 logdir
+ ".srv.%d" % (i
+ 1),
481 subprocess
.check_call([os
.path
.join(scriptsdir
, 'combine-codecov.sh'),
483 print "file://%s/index.html" % logdir
484 logger
.info("Code coverage report: file://%s/index.html" % logdir
)
486 if double_failed
or (failed
and not rerun_failures
):
487 logger
.info("Test run complete - failures found")
490 logger
.info("Test run complete - failures found on first run; passed on retry")
492 logger
.info("Test run complete - no failures")
495 if __name__
== "__main__":