vpp_lite: add cpu pinning support (VPP-467)
[vpp.git] / test / framework.py
1 #!/usr/bin/env python
2 ## @package framework
3 #  Module to handle test case execution.
4 #
5 #  The module provides a set of tools for constructing and running tests and
6 #  representing the results.
7
8 import logging
9 logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
10
11 import os
12 import subprocess
13 import unittest
14 from inspect import getdoc
15
16 from scapy.utils import wrpcap, rdpcap
17 from scapy.packet import Raw
18
19 ## Static variables to store color formatting strings.
20 #
21 #  These variables (RED, GREEN, YELLOW and LPURPLE) are used to configure
22 #  the color of the text to be printed in the terminal. Variable END is used
23 #  to revert the text color to the default one.
24 RED = '\033[91m'
25 GREEN = '\033[92m'
26 YELLOW = '\033[93m'
27 LPURPLE = '\033[94m'
28 END = '\033[0m'
29
30 ## Private class to create packet info object.
31 #
32 #  Help process information about the next packet.
33 #  Set variables to default values.
34 class _PacketInfo(object):
35     index = -1
36     src = -1
37     dst = -1
38     data = None
39     ## @var index
40     #  Integer variable to store the index of the packet.
41     ## @var src
42     #  Integer variable to store the index of the source packet generator
43     #  interface of the packet.
44     ## @var dst
45     #  Integer variable to store the index of the destination packet generator
46     #  interface of the packet.
47     ## @var data
48     #  Object variable to store the copy of the former packet.
49
50 ## Subclass of the python unittest.TestCase class.
51 #
52 #  This subclass is a base class for test cases that are implemented as classes.
53 #  It provides methods to create and run test case.
54 class VppTestCase(unittest.TestCase):
55
56     ## Class method to set class constants necessary to run test case.
57     #  @param cls The class pointer.
58     @classmethod
59     def setUpConstants(cls):
60         cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
61         cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
62         cls.vpp_api_test_bin = os.getenv("VPP_TEST_API_TEST_BIN",
63                                          "vpp-api-test")
64         cls.vpp_cmdline = [cls.vpp_bin, "unix", "nodaemon", "api-segment", "{",
65                            "prefix", "unittest", "}"]
66         if cls.plugin_path is not None:
67             cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
68
69         cls.vpp_api_test_cmdline = [cls.vpp_api_test_bin, "chroot", "prefix",
70                                     "unittest"]
71         try:
72             cls.verbose = int(os.getenv("V", 0))
73         except:
74             cls.verbose = 0
75
76         ## @var vpp_bin
77         #  String variable to store the path to vpp (vector packet processor).
78         ## @var vpp_api_test_bin
79         #  String variable to store the path to vpp_api_test (vpp API test tool).
80         ## @var vpp_cmdline
81         #  List of command line attributes for vpp.
82         ## @var vpp_api_test_cmdline
83         #  List of command line attributes for vpp_api_test.
84         ## @var verbose
85         #  Integer variable to store required verbosity level.
86
87     ## Class method to start the test case.
88     #  1. Initiate test case constants and set test case variables to default
89     #  values.
90     #  2. Remove files from the shared memory.
91     #  3. Start vpp as a subprocess.
92     #  @param cls The class pointer.
93     @classmethod
94     def setUpClass(cls):
95         cls.setUpConstants()
96         cls.pg_streams = []
97         cls.MY_MACS = {}
98         cls.MY_IP4S = {}
99         cls.MY_IP6S = {}
100         cls.VPP_MACS = {}
101         cls.VPP_IP4S = {}
102         cls.VPP_IP6S = {}
103         cls.packet_infos = {}
104         print "=================================================================="
105         print YELLOW + getdoc(cls) + END
106         print "=================================================================="
107         os.system("rm -f /dev/shm/unittest-global_vm")
108         os.system("rm -f /dev/shm/unittest-vpe-api")
109         os.system("rm -f /dev/shm/unittest-db")
110         cls.vpp = subprocess.Popen(cls.vpp_cmdline, stderr=subprocess.PIPE)
111         ## @var pg_streams
112         #  List variable to store packet-generator streams for interfaces.
113         ## @var MY_MACS
114         #  Dictionary variable to store host MAC addresses connected to packet
115         #  generator interfaces.
116         ## @var MY_IP4S
117         #  Dictionary variable to store host IPv4 addresses connected to packet
118         #  generator interfaces.
119         ## @var MY_IP6S
120         #  Dictionary variable to store host IPv6 addresses connected to packet
121         #  generator interfaces.
122         ## @var VPP_MACS
123         #  Dictionary variable to store VPP MAC addresses of the packet
124         #  generator interfaces.
125         ## @var VPP_IP4S
126         #  Dictionary variable to store VPP IPv4 addresses of the packet
127         #  generator interfaces.
128         ## @var VPP_IP6S
129         #  Dictionary variable to store VPP IPv6 addresses of the packet
130         #  generator interfaces.
131         ## @var vpp
132         #  Test case object variable to store file descriptor of running vpp
133         #  subprocess with open pipe to the standard error stream per
134         #  VppTestCase object.
135
136     ## Class method to do cleaning when all tests (test_) defined for
137     #  VppTestCase class are finished.
138     #  1. Terminate vpp and kill all vpp instances.
139     #  2. Remove files from the shared memory.
140     #  @param cls The class pointer.
141     @classmethod
142     def quit(cls):
143         cls.vpp.terminate()
144         cls.vpp = None
145         os.system("rm -f /dev/shm/unittest-global_vm")
146         os.system("rm -f /dev/shm/unittest-vpe-api")
147         os.system("rm -f /dev/shm/unittest-db")
148
149     ## Class method to define tear down action of the VppTestCase class.
150     #  @param cls The class pointer.
151     @classmethod
152     def tearDownClass(cls):
153         cls.quit()
154
155     ## Method to define tear down VPP actions of the test case.
156     #  @param self The object pointer.
157     def tearDown(self):
158         self.cli(2, "show int")
159         self.cli(2, "show trace")
160         self.cli(2, "show hardware")
161         self.cli(2, "show ip arp")
162         self.cli(2, "show ip fib")
163         self.cli(2, "show error")
164         self.cli(2, "show run")
165
166     ## Method to define setup action of the test case.
167     #  @param self The object pointer.
168     def setUp(self):
169         self.cli(2, "clear trace")
170
171     ## Class method to print logs.
172     #  Based on set level of verbosity print text in the terminal.
173     #  @param cls The class pointer.
174     #  @param s String variable to store text to be printed.
175     #  @param v Integer variable to store required level of verbosity.
176     @classmethod
177     def log(cls, s, v=1):
178         if cls.verbose >= v:
179             print "LOG: " + LPURPLE + s + END
180
181     ## Class method to execute api commands.
182     #  Based on set level of verbosity print the output of the api command in
183     #  the terminal.
184     #  @param cls The class pointer.
185     #  @param s String variable to store api command string.
186     @classmethod
187     def api(cls, s):
188         p = subprocess.Popen(cls.vpp_api_test_cmdline,
189                              stdout=subprocess.PIPE,
190                              stdin=subprocess.PIPE,
191                              stderr=subprocess.PIPE)
192         if cls.verbose > 0:
193             print "API: " + RED + s + END
194         p.stdin.write(s)
195         out = p.communicate()[0]
196         out = out.replace("vat# ", "", 2)
197         if cls.verbose > 0:
198             if len(out) > 1:
199                 print YELLOW + out + END
200         ## @var p
201         #  Object variable to store file descriptor of vpp_api_test subprocess
202         #  with open pipes to the standard output, inputs and error streams.
203         ## @var out
204         #  Tuple variable to store standard output of vpp_api_test subprocess
205         #  where the string "vat# " is replaced by empty string later.
206
207     ## Class method to execute cli commands.
208     #  Based on set level of verbosity of the log and verbosity defined by
209     #  environmental variable execute the cli command and print the output in
210     #  the terminal.
211     #  CLI command is executed via vpp API test tool (exec + cli_command)
212     #  @param cls The class pointer.
213     #  @param v Integer variable to store required level of verbosity.
214     #  @param s String variable to store cli command string.
215     @classmethod
216     def cli(cls, v, s):
217         if cls.verbose < v:
218             return
219         p = subprocess.Popen(cls.vpp_api_test_cmdline,
220                              stdout=subprocess.PIPE,
221                              stdin=subprocess.PIPE,
222                              stderr=subprocess.PIPE)
223         if cls.verbose > 0:
224             print "CLI: " + RED + s + END
225         p.stdin.write('exec ' + s)
226         out = p.communicate()[0]
227         out = out.replace("vat# ", "", 2)
228         if cls.verbose > 0:
229             if len(out) > 1:
230                 print YELLOW + out + END
231         ## @var p
232         #  Object variable to store file descriptor of vpp_api_test subprocess
233         #  with open pipes to the standard output, inputs and error streams.
234         ## @var out
235         #  Tuple variable to store standard output of vpp_api_test subprocess
236         #  where the string "vat# " is replaced by empty string later.
237
238     ## Class method to create incoming packet stream for the packet-generator
239     #  interface.
240     #  Delete old /tmp/pgX_in.pcap file if exists and create the empty one and
241     #  fill it with provided packets and add it to pg_streams list.
242     #  @param cls The class pointer.
243     #  @param i Integer variable to store the index of the packet-generator
244     #  interface to create packet stream for.
245     #  @param pkts List variable to store packets to be added to the stream.
246     @classmethod
247     def pg_add_stream(cls, i, pkts):
248         os.system("rm -f /tmp/pg%u_in.pcap" % i)
249         wrpcap("/tmp/pg%u_in.pcap" % i, pkts)
250         # no equivalent API command
251         cls.cli(0, "packet-generator new pcap /tmp/pg%u_in.pcap source pg%u"
252                    " name pcap%u" % (i, i, i))
253         cls.pg_streams.append('pcap%u' % i)
254
255     ## Class method to enable packet capturing for the packet-generator
256     #  interface.
257     #  Delete old /tmp/pgX_out.pcap file if exists and set the packet-generator
258     #  to capture outgoing packets to /tmp/pgX_out.pcap file.
259     #  @param cls The class pointer.
260     #  @param args List variable to store the indexes of the packet-generator
261     #  interfaces to start packet capturing for.
262     @classmethod
263     def pg_enable_capture(cls, args):
264         for i in args:
265             os.system("rm -f /tmp/pg%u_out.pcap" % i)
266             cls.cli(0, "packet-generator capture pg%u pcap /tmp/pg%u_out.pcap"
267                     % (i, i))
268
269     ## Class method to start packet sending.
270     #  Start to send packets for all defined pg streams. Delete every stream
271     #  from the stream list when sent and clear the pg_streams list.
272     #  @param cls The class pointer.
273     @classmethod
274     def pg_start(cls):
275         cls.cli(2, "trace add pg-input 50")  # 50 is maximum
276         cls.cli(0, 'packet-generator enable')
277         for stream in cls.pg_streams:
278             cls.cli(0, 'packet-generator delete %s' % stream)
279         cls.pg_streams = []
280
281     ## Class method to return captured packets.
282     #  Return packet captured for the defined packet-generator interface. Open
283     #  the corresponding pcap file (/tmp/pgX_out.pcap), read the content and
284     #  store captured packets to output variable.
285     #  @param cls The class pointer.
286     #  @param o Integer variable to store the index of the packet-generator
287     #  interface.
288     #  @return output List of packets captured on the defined packet-generator
289     #  interface. If the corresponding pcap file (/tmp/pgX_out.pcap) does not
290     #  exist return empty list.
291     @classmethod
292     def pg_get_capture(cls, o):
293         pcap_filename = "/tmp/pg%u_out.pcap" % o
294         try:
295             output = rdpcap(pcap_filename)
296         except IOError:  # TODO
297             cls.log("WARNING: File %s does not exist, probably because no"
298                     " packets arrived" % pcap_filename)
299             return []
300         return output
301         ## @var pcap_filename
302         #  File descriptor to the corresponding pcap file.
303
304     ## Class method to create packet-generator interfaces.
305     #  Create packet-generator interfaces and add host MAC addresses connected
306     #  to these packet-generator interfaces to the MY_MACS dictionary.
307     #  @param cls The class pointer.
308     #  @param args List variable to store the indexes of the packet-generator
309     #  interfaces to be created.
310     @classmethod
311     def create_interfaces(cls, args):
312         for i in args:
313             cls.MY_MACS[i] = "02:00:00:00:ff:%02x" % i
314             cls.log("My MAC address is %s" % (cls.MY_MACS[i]))
315             cls.api("pg_create_interface if_id %u" % i)
316             cls.api("sw_interface_set_flags pg%u admin-up" % i)
317
318     ## Static method to extend packet to specified size
319     #  Extend provided packet to the specified size (including Ethernet FCS).
320     #  The packet is extended by adding corresponding number of spaces to the
321     #  packet payload.
322     #  NOTE: Currently works only when Raw layer is present.
323     #  @param packet Variable to store packet object.
324     #  @param size Integer variable to store the required size of the packet.
325     @staticmethod
326     def extend_packet(packet, size):
327         packet_len = len(packet) + 4
328         extend = size - packet_len
329         if extend > 0:
330             packet[Raw].load += ' ' * extend
331         ## @var packet_len
332         #  Integer variable to store the current packet length including
333         #  Ethernet FCS.
334         ## @var extend
335         #  Integer variable to store the size of the packet extension.
336
337     ## Method to add packet info object to the packet_infos list.
338     #  Extend the existing packet_infos list with the given information from
339     #  the packet.
340     #  @param self The object pointer.
341     #  @param info Object to store required information from the packet.
342     def add_packet_info_to_list(self, info):
343         info.index = len(self.packet_infos)
344         self.packet_infos[info.index] = info
345         ## @var info.index
346         # Info object attribute to store the packet order in the stream.
347         ## @var packet_infos
348         #  List variable to store required information from packets.
349
350     ## Method to create packet info object.
351     #  Create the existing packet_infos list with the given information from
352     #  the packet.
353     #  @param self The object pointer.
354     #  @param pg_id Integer variable to store the index of the packet-generator
355     #  interface.
356     def create_packet_info(self, pg_id, target_id):
357         info = _PacketInfo()
358         self.add_packet_info_to_list(info)
359         info.src = pg_id
360         info.dst = target_id
361         return info
362         ## @var info
363         #  Object to store required information from packet.
364         ## @var info.src
365         #  Info object attribute to store the index of the source packet
366         #  generator interface of the packet.
367         ## @var info.dst
368         #  Info object attribute to store the index of the destination packet
369         #  generator interface of the packet.
370
371     ## Static method to return packet info string.
372     #  Create packet info string from the provided info object that will be put
373     #  to the packet payload.
374     #  @param info Object to store required information from the packet.
375     #  @return String of information about packet's order in the stream, source
376     #  and destination packet generator interface.
377     @staticmethod
378     def info_to_payload(info):
379         return "%d %d %d" % (info.index, info.src, info.dst)
380
381     ## Static method to create packet info object from the packet payload.
382     #  Create packet info object and set its attribute values based on data
383     #  gained from the packet payload.
384     #  @param payload String variable to store packet payload.
385     #  @return info Object to store required information about the packet.
386     @staticmethod
387     def payload_to_info(payload):
388         numbers = payload.split()
389         info = _PacketInfo()
390         info.index = int(numbers[0])
391         info.src = int(numbers[1])
392         info.dst = int(numbers[2])
393         return info
394         ## @var info.index
395         #  Info object attribute to store the packet order in the stream.
396         ## @var info.src
397         #  Info object attribute to store the index of the source packet
398         #  generator interface of the packet.
399         ## @var info.dst
400         #  Info object attribute to store the index of the destination packet
401         #  generator interface of the packet.
402
403     ## Method to return packet info object of the next packet in
404     #  the packet_infos list.
405     #  Get the next packet info object from the packet_infos list by increasing
406     #  the packet_infos list index by one.
407     #  @param self The object pointer.
408     #  @param info Object to store required information about the packet.
409     #  @return packet_infos[next_index] Next info object from the packet_infos
410     #  list with stored information about packets. Return None if the end of
411     #  the list is reached.
412     def get_next_packet_info(self, info):
413         if info is None:
414             next_index = 0
415         else:
416             next_index = info.index + 1
417         if next_index == len(self.packet_infos):
418             return None
419         else:
420             return self.packet_infos[next_index]
421         ## @var next_index
422         #  Integer variable to store the index of the next info object.
423
424     ## Method to return packet info object of the next packet with the required
425     #  source packet generator interface.
426     #  Iterate over the packet_infos list and search for the next packet info
427     #  object with the required source packet generator interface.
428     #  @param self The object pointer.
429     #  @param src_pg Integer variable to store index of requested source packet
430     #  generator interface.
431     #  @param info Object to store required information about the packet.
432     #  @return packet_infos[next_index] Next info object from the packet_infos
433     #  list with stored information about packets. Return None if the end of
434     #  the list is reached.
435     def get_next_packet_info_for_interface(self, src_pg, info):
436         while True:
437             info = self.get_next_packet_info(info)
438             if info is None:
439                 return None
440             if info.src == src_pg:
441                 return info
442         ## @var info.src
443         #  Info object attribute to store the index of the source packet
444         #  generator interface of the packet.
445
446     ## Method to return packet info object of the next packet with required
447     #  source and destination packet generator interfaces.
448     #  Search for the next packet info object with the required source and
449     #  destination packet generator interfaces.
450     #  @param self The object pointer.
451     #  @param src_pg Integer variable to store the index of the requested source
452     #  packet generator interface.
453     #  @param dst_pg Integer variable to store the index of the requested source
454     #  packet generator interface.
455     #  @param info Object to store required information about the packet.
456     #  @return info Object with the info about the next packet with with
457     #  required source and destination packet generator interfaces. Return None
458     #  if there is no other packet with required data.
459     def get_next_packet_info_for_interface2(self, src_pg, dst_pg, info):
460         while True:
461             info = self.get_next_packet_info_for_interface(src_pg, info)
462             if info is None:
463                 return None
464             if info.dst == dst_pg:
465                 return info
466         ## @var info.dst
467         #  Info object attribute to store the index of the destination packet
468         #  generator interface of the packet.
469
470
471 ## Subclass of the python unittest.TestResult class.
472 #
473 #  This subclass provides methods to compile information about which tests have
474 #  succeeded and which have failed.
475 class VppTestResult(unittest.TestResult):
476     ## The constructor.
477     #  @param stream File descriptor to store where to report test results. Set
478     #  to the standard error stream by default.
479     #  @param descriptions Boolean variable to store information if to use test
480     #  case descriptions.
481     #  @param verbosity Integer variable to store required verbosity level.
482     def __init__(self, stream, descriptions, verbosity):
483         unittest.TestResult.__init__(self, stream, descriptions, verbosity)
484         self.stream = stream
485         self.descriptions = descriptions
486         self.verbosity = verbosity
487         self.result_string = None
488         ## @var result_string
489         #  String variable to store the test case result string.
490
491
492     ## Method called when the test case succeeds.
493     #  Run the default implementation (that does nothing) and set the result
494     #  string in case of test case success.
495     #  @param self The object pointer.
496     #  @param test Object variable to store the test case instance.
497     def addSuccess(self, test):
498         unittest.TestResult.addSuccess(self, test)
499         self.result_string = GREEN + "OK" + END
500         ## @var result_string
501         #  String variable to store the test case result string.
502
503     ## Method called when the test case signals a failure.
504     #  Run the default implementation that appends a tuple (test, formatted_err)
505     #  to the instance's failures attribute, where formatted_err is a formatted
506     #  traceback derived from err and set the result string in case of test case
507     #  success.
508     #  @param self The object pointer.
509     #  @param test Object variable to store the test case instance.
510     #  @param err Tuple variable to store the error data:
511     #  (type, value, traceback).
512     def addFailure(self, test, err):
513         unittest.TestResult.addFailure(self, test, err)
514         self.result_string = RED + "FAIL" + END
515         ## @var result_string
516         #  String variable to store the test case result string.
517
518     ## Method called when the test case raises an unexpected exception.
519     #  Run the default implementation that appends a tuple (test, formatted_err)
520     #  to the instance's error attribute, where formatted_err is a formatted
521     #  traceback derived from err and set the result string in case of test case
522     #  unexpected failure.
523     #  @param self The object pointer.
524     #  @param test Object variable to store the test case instance.
525     #  @param err Tuple variable to store the error data:
526     #  (type, value, traceback).
527     def addError(self, test, err):
528         unittest.TestResult.addError(self, test, err)
529         self.result_string = RED + "ERROR" + END
530         ## @var result_string
531         #  String variable to store the test case result string.
532
533     ## Method to get the description of the test case.
534     #  Used to get the description string from the test case object.
535     #  @param self The object pointer.
536     #  @param test Object variable to store the test case instance.
537     #  @return String of the short description if exist otherwise return test
538     #  case name string.
539     def getDescription(self, test):
540         # TODO: if none print warning not raise exception
541         short_description = test.shortDescription()
542         if self.descriptions and short_description:
543             return short_description
544         else:
545             return str(test)
546         ## @var short_description
547         #  String variable to store the short description of the test case.
548
549     ## Method called when the test case is about to be run.
550     #  Run the default implementation and based on the set verbosity level write
551     #  the starting string to the output stream.
552     #  @param self The object pointer.
553     #  @param test Object variable to store the test case instance.
554     def startTest(self, test):
555         unittest.TestResult.startTest(self, test)
556         if self.verbosity > 0:
557             self.stream.writeln("Starting " + self.getDescription(test) + " ...")
558             self.stream.writeln("------------------------------------------------------------------")
559
560     ## Method called after the test case has been executed.
561     #  Run the default implementation and based on the set verbosity level write
562     #  the result string to the output stream.
563     #  @param self The object pointer.
564     #  @param test Object variable to store the test case instance.
565     def stopTest(self, test):
566         unittest.TestResult.stopTest(self, test)
567         if self.verbosity > 0:
568             self.stream.writeln("------------------------------------------------------------------")
569             self.stream.writeln("%-60s%s" % (self.getDescription(test), self.result_string))
570             self.stream.writeln("------------------------------------------------------------------")
571         else:
572             self.stream.writeln("%-60s%s" % (self.getDescription(test), self.result_string))
573
574     ## Method to write errors and failures information to the output stream.
575     #  Write content of errors and failures lists to the output stream.
576     #  @param self The object pointer.
577     def printErrors(self):
578         self.stream.writeln()
579         self.printErrorList('ERROR', self.errors)
580         self.printErrorList('FAIL', self.failures)
581         ## @var errors
582         #  List variable containing 2-tuples of TestCase instances and strings
583         #  holding formatted tracebacks. Each tuple represents a test which
584         #  raised an unexpected exception.
585         ## @var failures
586         #  List variable containing 2-tuples of TestCase instances and strings
587         #  holding formatted tracebacks. Each tuple represents a test where
588         #  a failure was explicitly signalled using the TestCase.assert*()
589         #  methods.
590
591     ## Method to write the error information to the output stream.
592     #  Write content of error lists to the output stream together with error
593     #  type and test case description.
594     #  @param self The object pointer.
595     #  @param flavour String variable to store error type.
596     #  @param errors List variable to store 2-tuples of TestCase instances and
597     #  strings holding formatted tracebacks.
598     def printErrorList(self, flavour, errors):
599         for test, err in errors:
600             self.stream.writeln('=' * 70)
601             self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
602             self.stream.writeln('-' * 70)
603             self.stream.writeln("%s" % err)
604         ## @var test
605         #  Object variable to store the test case instance.
606         ## @var err
607         #  String variable to store formatted tracebacks.
608
609
610 ## Subclass of the python unittest.TextTestRunner class.
611 #
612 #  A basic test runner implementation which prints results on standard error.
613 class VppTestRunner(unittest.TextTestRunner):
614     ##  Class object variable to store the results of a set of tests.
615     resultclass = VppTestResult
616
617     ## Method to run the test.
618     #  Print debug message in the terminal and run the standard run() method
619     #  of the test runner collecting the result into the test result object.
620     #  @param self The object pointer.
621     #  @param test Object variable to store the test case instance.
622     #  @return Test result object of the VppTestRunner.
623     def run(self, test):
624         print "Running tests using custom test runner"  # debug message
625         return super(VppTestRunner, self).run(test)