make test: enhance core-file information
[vpp.git] / test / hook.py
1 import signal
2 import os
3 import sys
4 import traceback
5 from log import RED, single_line_delim, double_line_delim
6 from debug import spawn_gdb
7 from subprocess import check_output, CalledProcessError
8
9
10 class Hook(object):
11     """
12     Generic hooks before/after API/CLI calls
13     """
14
15     def __init__(self, logger):
16         self.logger = logger
17
18     def before_api(self, api_name, api_args):
19         """
20         Function called before API call
21         Emit a debug message describing the API name and arguments
22
23         @param api_name: name of the API
24         @param api_args: tuple containing the API arguments
25         """
26         self.logger.debug("API: %s (%s)" %
27                           (api_name, api_args), extra={'color': RED})
28
29     def after_api(self, api_name, api_args):
30         """
31         Function called after API call
32
33         @param api_name: name of the API
34         @param api_args: tuple containing the API arguments
35         """
36         pass
37
38     def before_cli(self, cli):
39         """
40         Function called before CLI call
41         Emit a debug message describing the CLI
42
43         @param cli: CLI string
44         """
45         self.logger.debug("CLI: %s" % (cli), extra={'color': RED})
46
47     def after_cli(self, cli):
48         """
49         Function called after CLI call
50         """
51         pass
52
53
54 class VppDiedError(Exception):
55     pass
56
57
58 class PollHook(Hook):
59     """ Hook which checks if the vpp subprocess is alive """
60
61     def __init__(self, testcase):
62         self.testcase = testcase
63         self.logger = testcase.logger
64
65     def on_crash(self, core_path):
66         if self.testcase.debug_core:
67             # notify parent process that we're handling a core file
68             open('%s/_core_handled' % self.testcase.tempdir, 'a').close()
69             spawn_gdb(self.testcase.vpp_bin, core_path, self.logger)
70         else:
71             self.logger.error("Core file present, debug with: gdb %s %s" %
72                               (self.testcase.vpp_bin, core_path))
73             self.logger.error("Running `file %s':" % core_path)
74             try:
75                 info = check_output(["file", core_path])
76                 self.logger.error(info)
77             except CalledProcessError as e:
78                 self.logger.error(
79                     "Could not run `file' utility on core-file, "
80                     "rc=%s" % e.returncode)
81                 pass
82
83     def poll_vpp(self):
84         """
85         Poll the vpp status and throw an exception if it's not running
86         :raises VppDiedError: exception if VPP is not running anymore
87         """
88         if self.testcase.vpp_dead:
89             # already dead, nothing to do
90             return
91
92         self.testcase.vpp.poll()
93         if self.testcase.vpp.returncode is not None:
94             signaldict = dict(
95                 (k, v) for v, k in reversed(sorted(signal.__dict__.items()))
96                 if v.startswith('SIG') and not v.startswith('SIG_'))
97
98             if self.testcase.vpp.returncode in signaldict:
99                 s = signaldict[abs(self.testcase.vpp.returncode)]
100             else:
101                 s = "unknown"
102             msg = "VPP subprocess died unexpectedly with returncode %d [%s]" %\
103                 (self.testcase.vpp.returncode, s)
104             self.logger.critical(msg)
105             core_path = self.testcase.tempdir + '/core'
106             if os.path.isfile(core_path):
107                 self.on_crash(core_path)
108             self.testcase.vpp_dead = True
109             raise VppDiedError(msg)
110
111     def before_api(self, api_name, api_args):
112         """
113         Check if VPP died before executing an API
114
115         :param api_name: name of the API
116         :param api_args: tuple containing the API arguments
117         :raises VppDiedError: exception if VPP is not running anymore
118
119         """
120         super(PollHook, self).before_api(api_name, api_args)
121         self.poll_vpp()
122
123     def before_cli(self, cli):
124         """
125         Check if VPP died before executing a CLI
126
127         :param cli: CLI string
128         :raises Exception: exception if VPP is not running anymore
129
130         """
131         super(PollHook, self).before_cli(cli)
132         self.poll_vpp()
133
134
135 class StepHook(PollHook):
136     """ Hook which requires user to press ENTER before doing any API/CLI """
137
138     def __init__(self, testcase):
139         self.skip_stack = None
140         self.skip_num = None
141         self.skip_count = 0
142         super(StepHook, self).__init__(testcase)
143
144     def skip(self):
145         if self.skip_stack is None:
146             return False
147         stack = traceback.extract_stack()
148         counter = 0
149         skip = True
150         for e in stack:
151             if counter > self.skip_num:
152                 break
153             if e[0] != self.skip_stack[counter][0]:
154                 skip = False
155             if e[1] != self.skip_stack[counter][1]:
156                 skip = False
157             counter += 1
158         if skip:
159             self.skip_count += 1
160             return True
161         else:
162             print("%d API/CLI calls skipped in specified stack "
163                   "frame" % self.skip_count)
164             self.skip_count = 0
165             self.skip_stack = None
166             self.skip_num = None
167             return False
168
169     def user_input(self):
170         print('number\tfunction\tfile\tcode')
171         counter = 0
172         stack = traceback.extract_stack()
173         for e in stack:
174             print('%02d.\t%s\t%s:%d\t[%s]' % (counter, e[2], e[0], e[1], e[3]))
175             counter += 1
176         print(single_line_delim)
177         print("You can enter a number of stack frame chosen from above")
178         print("Calls in/below that stack frame will be not be stepped anymore")
179         print(single_line_delim)
180         while True:
181             choice = sys.stdin.readline(
182                 "Enter your choice, if any, and press ENTER to continue "
183                 "running the testcase...")
184             if choice == "":
185                 choice = None
186             try:
187                 if choice is not None:
188                     num = int(choice)
189             except TypeError:
190                 print("Invalid input")
191                 continue
192             if choice is not None and (num < 0 or num >= len(stack)):
193                 print("Invalid choice")
194                 continue
195             break
196         if choice is not None:
197             self.skip_stack = stack
198             self.skip_num = num
199
200     def before_cli(self, cli):
201         """ Wait for ENTER before executing CLI """
202         if self.skip():
203             print("Skip pause before executing CLI: %s" % cli)
204         else:
205             print(double_line_delim)
206             print("Test paused before executing CLI: %s" % cli)
207             print(single_line_delim)
208             self.user_input()
209         super(StepHook, self).before_cli(cli)
210
211     def before_api(self, api_name, api_args):
212         """ Wait for ENTER before executing API """
213         if self.skip():
214             print("Skip pause before executing API: %s (%s)"
215                   % (api_name, api_args))
216         else:
217             print(double_line_delim)
218             print("Test paused before executing API: %s (%s)"
219                   % (api_name, api_args))
220             print(single_line_delim)
221             self.user_input()
222         super(StepHook, self).before_api(api_name, api_args)