make test: detect child crash
[vpp.git] / test / run_tests.py
1 #!/usr/bin/env python
2
3 import sys
4 import shutil
5 import os
6 import select
7 import unittest
8 import argparse
9 import time
10 from multiprocessing import Process, Pipe
11 from framework import VppTestRunner
12 from debug import spawn_gdb
13 from log import global_logger
14 from discover_tests import discover_tests
15
16
17 def test_runner_wrapper(suite, keep_alive_pipe, result_pipe, failed_pipe):
18     result = not VppTestRunner(
19         keep_alive_pipe=keep_alive_pipe,
20         failed_pipe=failed_pipe,
21         verbosity=verbose,
22         failfast=failfast).run(suite).wasSuccessful()
23     result_pipe.send(result)
24     result_pipe.close()
25     keep_alive_pipe.close()
26     failed_pipe.close()
27
28
29 class add_to_suite_callback:
30     def __init__(self, suite):
31         self.suite = suite
32
33     def __call__(self, file_name, cls, method):
34         suite.addTest(cls(method))
35
36
37 class Filter_by_class_list:
38     def __init__(self, class_list):
39         self.class_list = class_list
40
41     def __call__(self, file_name, class_name, func_name):
42         return class_name in self.class_list
43
44
45 def suite_from_failed(suite, failed):
46     filter_cb = Filter_by_class_list(failed)
47     return VppTestRunner.filter_tests(suite, filter_cb)
48
49
50 def run_forked(suite):
51     keep_alive_parent_end, keep_alive_child_end = Pipe(duplex=False)
52     result_parent_end, result_child_end = Pipe(duplex=False)
53     failed_parent_end, failed_child_end = Pipe(duplex=False)
54
55     child = Process(target=test_runner_wrapper,
56                     args=(suite, keep_alive_child_end, result_child_end,
57                           failed_child_end))
58     child.start()
59     last_test_temp_dir = None
60     last_test_vpp_binary = None
61     last_test = None
62     result = None
63     failed = set()
64     last_heard = time.time()
65     while True:
66         readable = select.select([keep_alive_parent_end.fileno(),
67                                   result_parent_end.fileno(),
68                                   failed_parent_end.fileno(),
69                                   ],
70                                  [], [], 1)[0]
71         if result_parent_end.fileno() in readable:
72             result = result_parent_end.recv()
73             break
74         if keep_alive_parent_end.fileno() in readable:
75             while keep_alive_parent_end.poll():
76                 last_test, last_test_vpp_binary,\
77                     last_test_temp_dir, vpp_pid = keep_alive_parent_end.recv()
78             last_heard = time.time()
79         if failed_parent_end.fileno() in readable:
80             while failed_parent_end.poll():
81                 failed_test = failed_parent_end.recv()
82                 failed.add(failed_test.__name__)
83             last_heard = time.time()
84         fail = False
85         if last_heard + test_timeout < time.time():
86             fail = True
87             global_logger.critical("Timeout while waiting for child test "
88                                    "runner process (last test running was "
89                                    "`%s' in `%s')!" %
90                                    (last_test, last_test_temp_dir))
91         elif not child.is_alive():
92             fail = True
93             global_logger.critical("Child process unexpectedly died (last "
94                                    "test running was `%s' in `%s')!" %
95                                    (last_test, last_test_temp_dir))
96         if fail:
97             failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
98             lttd = last_test_temp_dir.split("/")[-1]
99             link_path = '%s%s-FAILED' % (failed_dir, lttd)
100             global_logger.error("Creating a link to the failed " +
101                                 "test: %s -> %s" % (link_path, lttd))
102             os.symlink(last_test_temp_dir, link_path)
103             api_post_mortem_path = "/tmp/api_post_mortem.%d" % vpp_pid
104             if os.path.isfile(api_post_mortem_path):
105                 global_logger.error("Copying api_post_mortem.%d to %s" %
106                                     (vpp_pid, last_test_temp_dir))
107                 shutil.copy2(api_post_mortem_path, last_test_temp_dir)
108             if last_test_temp_dir and last_test_vpp_binary:
109                 core_path = "%s/core" % last_test_temp_dir
110                 if os.path.isfile(core_path):
111                     global_logger.error("Core-file exists in test temporary "
112                                         "directory: %s!" % core_path)
113                     if d and d.lower() == "core":
114                         spawn_gdb(last_test_vpp_binary, core_path,
115                                   global_logger)
116             child.terminate()
117             result = -1
118             break
119     keep_alive_parent_end.close()
120     result_parent_end.close()
121     failed_parent_end.close()
122     return result, failed
123
124
125 if __name__ == '__main__':
126
127     try:
128         verbose = int(os.getenv("V", 0))
129     except:
130         verbose = 0
131
132     default_test_timeout = 600  # 10 minutes
133     try:
134         test_timeout = int(os.getenv("TIMEOUT", default_test_timeout))
135     except:
136         test_timeout = default_test_timeout
137
138     try:
139         debug = os.getenv("DEBUG")
140     except:
141         debug = None
142
143     parser = argparse.ArgumentParser(description="VPP unit tests")
144     parser.add_argument("-f", "--failfast", action='count',
145                         help="fast failure flag")
146     parser.add_argument("-d", "--dir", action='append', type=str,
147                         help="directory containing test files "
148                              "(may be specified multiple times)")
149     args = parser.parse_args()
150     failfast = True if args.failfast == 1 else False
151
152     suite = unittest.TestSuite()
153     cb = add_to_suite_callback(suite)
154     for d in args.dir:
155         print("Adding tests from directory tree %s" % d)
156         discover_tests(d, cb)
157
158     try:
159         retries = int(os.getenv("RETRIES"))
160     except:
161         retries = 0
162     if retries is None:
163         retries = 0
164     attempts = retries + 1
165     if attempts > 1:
166         print("Perform %s attempts to pass the suite..." % attempts)
167     if debug is None or debug.lower() not in ["gdb", "gdbserver"]:
168         while True:
169             result, failed = run_forked(suite)
170             attempts = attempts - 1
171             print("%s test(s) failed, %s attempt(s) left" %
172                   (len(failed), attempts))
173             if len(failed) > 0 and attempts > 0:
174                 suite = suite_from_failed(suite, failed)
175                 continue
176             sys.exit(result)
177
178     # don't fork if debugging..
179     sys.exit(not VppTestRunner(verbosity=verbose,
180                                failfast=failfast).run(suite).wasSuccessful())