Damjan Marion | f56b77a | 2016-10-03 19:44:57 +0200 | [diff] [blame] | 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") |
Pierre Pfister | cd8e318 | 2016-10-07 16:30:03 +0100 | [diff] [blame] | 61 | cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH') |
Damjan Marion | f56b77a | 2016-10-03 19:44:57 +0200 | [diff] [blame] | 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", "}"] |
Pierre Pfister | cd8e318 | 2016-10-07 16:30:03 +0100 | [diff] [blame] | 66 | if cls.plugin_path is not None: |
| 67 | cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path]) |
| 68 | |
Damjan Marion | f56b77a | 2016-10-03 19:44:57 +0200 | [diff] [blame] | 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): |
Klement Sekera | b80f9d1 | 2016-10-05 14:05:33 +0200 | [diff] [blame] | 248 | os.system("rm -f /tmp/pg%u_in.pcap" % i) |
Damjan Marion | f56b77a | 2016-10-03 19:44:57 +0200 | [diff] [blame] | 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: |
Klement Sekera | b80f9d1 | 2016-10-05 14:05:33 +0200 | [diff] [blame] | 265 | os.system("rm -f /tmp/pg%u_out.pcap" % i) |
Damjan Marion | f56b77a | 2016-10-03 19:44:57 +0200 | [diff] [blame] | 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) |