tests: fix handling failed test case
[vpp.git] / test / hook.py
1 import os
2 import sys
3 import traceback
4 import ipaddress
5 from subprocess import check_output, CalledProcessError
6
7 import scapy.compat
8 import framework
9 from config import config
10 from log import RED, single_line_delim, double_line_delim
11 from util import check_core_path, get_core_path
12
13
14 class Hook:
15     """
16     Generic hooks before/after API/CLI calls
17     """
18
19     def __init__(self, test):
20         self.test = test
21         self.logger = test.logger
22
23     def before_api(self, api_name, api_args):
24         """
25         Function called before API call
26         Emit a debug message describing the API name and arguments
27
28         @param api_name: name of the API
29         @param api_args: tuple containing the API arguments
30         """
31
32         def _friendly_format(val):
33             if not isinstance(val, str):
34                 return val
35             if len(val) == 6:
36                 return '{!s} ({!s})'.format(val, ':'.join(['{:02x}'.format(
37                     scapy.compat.orb(x)) for x in val]))
38             try:
39                 # we don't call test_type(val) because it is a packed value.
40                 return '{!s} ({!s})'.format(val, str(
41                     ipaddress.ip_address(val)))
42             except ValueError:
43                 return val
44
45         _args = ', '.join("{!s}={!r}".format(key, _friendly_format(val)) for
46                           (key, val) in api_args.items())
47         self.logger.debug("API: %s (%s)" %
48                           (api_name, _args), extra={'color': RED})
49
50     def after_api(self, api_name, api_args):
51         """
52         Function called after API call
53
54         @param api_name: name of the API
55         @param api_args: tuple containing the API arguments
56         """
57         pass
58
59     def before_cli(self, cli):
60         """
61         Function called before CLI call
62         Emit a debug message describing the CLI
63
64         @param cli: CLI string
65         """
66         self.logger.debug("CLI: %s" % (cli), extra={'color': RED})
67
68     def after_cli(self, cli):
69         """
70         Function called after CLI call
71         """
72         pass
73
74
75 class PollHook(Hook):
76     """ Hook which checks if the vpp subprocess is alive """
77
78     def __init__(self, test):
79         super(PollHook, self).__init__(test)
80
81     def on_crash(self, core_path):
82         self.logger.error("Core file present, debug with: gdb %s %s",
83                           config.vpp, core_path)
84         check_core_path(self.logger, core_path)
85         self.logger.error("Running `file %s':", core_path)
86         try:
87             info = check_output(["file", core_path])
88             self.logger.error(info)
89         except CalledProcessError as e:
90             self.logger.error(
91                 "Subprocess returned with error running `file' utility on "
92                 "core-file, "
93                 "rc=%s",  e.returncode)
94         except OSError as e:
95             self.logger.error(
96                 "Subprocess returned OS error running `file' utility on "
97                 "core-file, "
98                 "oserror=(%s) %s", e.errno, e.strerror)
99         except Exception as e:
100             self.logger.error(
101                 "Subprocess returned unanticipated error running `file' "
102                 "utility on core-file, "
103                 "%s", e)
104
105     def poll_vpp(self):
106         """
107         Poll the vpp status and throw an exception if it's not running
108         :raises VppDiedError: exception if VPP is not running anymore
109         """
110         if self.test.vpp_dead:
111             # already dead, nothing to do
112             return
113
114         self.test.vpp.poll()
115         if self.test.vpp.returncode is not None:
116             self.test.vpp_dead = True
117             raise framework.VppDiedError(rv=self.test.vpp.returncode)
118             core_path = get_core_path(self.test.tempdir)
119             if os.path.isfile(core_path):
120                 self.on_crash(core_path)
121
122     def before_api(self, api_name, api_args):
123         """
124         Check if VPP died before executing an API
125
126         :param api_name: name of the API
127         :param api_args: tuple containing the API arguments
128         :raises VppDiedError: exception if VPP is not running anymore
129
130         """
131         super(PollHook, self).before_api(api_name, api_args)
132         self.poll_vpp()
133
134     def before_cli(self, cli):
135         """
136         Check if VPP died before executing a CLI
137
138         :param cli: CLI string
139         :raises Exception: exception if VPP is not running anymore
140
141         """
142         super(PollHook, self).before_cli(cli)
143         self.poll_vpp()
144
145
146 class StepHook(PollHook):
147     """ Hook which requires user to press ENTER before doing any API/CLI """
148
149     def __init__(self, test):
150         self.skip_stack = None
151         self.skip_num = None
152         self.skip_count = 0
153         self.break_func = None
154         super(StepHook, self).__init__(test)
155
156     def skip(self):
157         if self.break_func is not None:
158             return self.should_skip_func_based()
159         if self.skip_stack is not None:
160             return self.should_skip_stack_based()
161
162     def should_skip_func_based(self):
163         stack = traceback.extract_stack()
164         for e in stack:
165             if e[2] == self.break_func:
166                 self.break_func = None
167                 return False
168         return True
169
170     def should_skip_stack_based(self):
171         stack = traceback.extract_stack()
172         counter = 0
173         skip = True
174         for e in stack:
175             if counter > self.skip_num:
176                 break
177             if e[0] != self.skip_stack[counter][0]:
178                 skip = False
179             if e[1] != self.skip_stack[counter][1]:
180                 skip = False
181             counter += 1
182         if skip:
183             self.skip_count += 1
184             return True
185         else:
186             print("%d API/CLI calls skipped in specified stack "
187                   "frame" % self.skip_count)
188             self.skip_count = 0
189             self.skip_stack = None
190             self.skip_num = None
191             return False
192
193     def user_input(self):
194         print('number\tfunction\tfile\tcode')
195         counter = 0
196         stack = traceback.extract_stack()
197         for e in stack:
198             print('%02d.\t%s\t%s:%d\t[%s]' % (counter, e[2], e[0], e[1], e[3]))
199             counter += 1
200         print(single_line_delim)
201         print("You may enter a number of stack frame chosen from above")
202         print("Calls in/below that stack frame will be not be stepped anymore")
203         print("Alternatively, enter a test function name to stop at")
204         print(single_line_delim)
205         while True:
206             print("Enter your choice, if any, and press ENTER to continue "
207                   "running the testcase...")
208             choice = sys.stdin.readline().rstrip('\r\n')
209             if choice == "":
210                 choice = None
211             try:
212                 if choice is not None:
213                     num = int(choice)
214             except ValueError:
215                 if choice.startswith("test_"):
216                     break
217                 print("Invalid input")
218                 continue
219             if choice is not None and (num < 0 or num >= len(stack)):
220                 print("Invalid choice")
221                 continue
222             break
223         if choice is not None:
224             if choice.startswith("test_"):
225                 self.break_func = choice
226             else:
227                 self.break_func = None
228                 self.skip_stack = stack
229                 self.skip_num = num
230
231     def before_cli(self, cli):
232         """ Wait for ENTER before executing CLI """
233         if self.skip():
234             print("Skip pause before executing CLI: %s" % cli)
235         else:
236             print(double_line_delim)
237             print("Test paused before executing CLI: %s" % cli)
238             print(single_line_delim)
239             self.user_input()
240         super(StepHook, self).before_cli(cli)
241
242     def before_api(self, api_name, api_args):
243         """ Wait for ENTER before executing API """
244         if self.skip():
245             print("Skip pause before executing API: %s (%s)"
246                   % (api_name, api_args))
247         else:
248             print(double_line_delim)
249             print("Test paused before executing API: %s (%s)"
250                   % (api_name, api_args))
251             print(single_line_delim)
252             self.user_input()
253         super(StepHook, self).before_api(api_name, api_args)