aboutsummaryrefslogtreecommitdiffstats
path: root/test/framework.py
blob: 0ee266ec843618b9d089da777d3b5431d24b4c47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
#!/usr/bin/env python
## @package framework
#  Module to handle test case execution.
#
#  The module provides a set of tools for constructing and running tests and
#  representing the results.

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

import os
import subprocess
import unittest
from inspect import getdoc

from scapy.utils import wrpcap, rdpcap
from scapy.packet import Raw

## Static variables to store color formatting strings.
#
#  These variables (RED, GREEN, YELLOW and LPURPLE) are used to configure
#  the color of the text to be printed in the terminal. Variable END is used
#  to revert the text color to the default one.
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
LPURPLE = '\033[94m'
END = '\033[0m'

## Private class to create packet info object.
#
#  Help process information about the next packet.
#  Set variables to default values.
class _PacketInfo(object):
    index = -1
    src = -1
    dst = -1
    data = None
    ## @var index
    #  Integer variable to store the index of the packet.
    ## @var src
    #  Integer variable to store the index of the source packet generator
    #  interface of the packet.
    ## @var dst
    #  Integer variable to store the index of the destination packet generator
    #  interface of the packet.
    ## @var data
    #  Object variable to store the copy of the former packet.

## Subclass of the python unittest.TestCase class.
#
#  This subclass is a base class for test cases that are implemented as classes.
#  It provides methods to create and run test case.
class VppTestCase(unittest.TestCase):

    ## Class method to set class constants necessary to run test case.
    #  @param cls The class pointer.
    @classmethod
    def setUpConstants(cls):
        cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
        cls.vpp_api_test_bin = os.getenv("VPP_TEST_API_TEST_BIN",
                                         "vpp-api-test")
        cls.vpp_cmdline = [cls.vpp_bin, "unix", "nodaemon", "api-segment", "{",
                           "prefix", "unittest", "}"]
        cls.vpp_api_test_cmdline = [cls.vpp_api_test_bin, "chroot", "prefix",
                                    "unittest"]
        try:
            cls.verbose = int(os.getenv("V", 0))
        except:
            cls.verbose = 0

        ## @var vpp_bin
        #  String variable to store the path to vpp (vector packet processor).
        ## @var vpp_api_test_bin
        #  String variable to store the path to vpp_api_test (vpp API test tool).
        ## @var vpp_cmdline
        #  List of command line attributes for vpp.
        ## @var vpp_api_test_cmdline
        #  List of command line attributes for vpp_api_test.
        ## @var verbose
        #  Integer variable to store required verbosity level.

    ## Class method to start the test case.
    #  1. Initiate test case constants and set test case variables to default
    #  values.
    #  2. Remove files from the shared memory.
    #  3. Start vpp as a subprocess.
    #  @param cls The class pointer.
    @classmethod
    def setUpClass(cls):
        cls.setUpConstants()
        cls.pg_streams = []
        cls.MY_MACS = {}
        cls.MY_IP4S = {}
        cls.MY_IP6S = {}
        cls.VPP_MACS = {}
        cls.VPP_IP4S = {}
        cls.VPP_IP6S = {}
        cls.packet_infos = {}
        print "=================================================================="
        print YELLOW + getdoc(cls) + END
        print "=================================================================="
        os.system("rm -f /dev/shm/unittest-global_vm")
        os.system("rm -f /dev/shm/unittest-vpe-api")
        os.system("rm -f /dev/shm/unittest-db")
        cls.vpp = subprocess.Popen(cls.vpp_cmdline, stderr=subprocess.PIPE)
        ## @var pg_streams
        #  List variable to store packet-generator streams for interfaces.
        ## @var MY_MACS
        #  Dictionary variable to store host MAC addresses connected to packet
        #  generator interfaces.
        ## @var MY_IP4S
        #  Dictionary variable to store host IPv4 addresses connected to packet
        #  generator interfaces.
        ## @var MY_IP6S
        #  Dictionary variable to store host IPv6 addresses connected to packet
        #  generator interfaces.
        ## @var VPP_MACS
        #  Dictionary variable to store VPP MAC addresses of the packet
        #  generator interfaces.
        ## @var VPP_IP4S
        #  Dictionary variable to store VPP IPv4 addresses of the packet
        #  generator interfaces.
        ## @var VPP_IP6S
        #  Dictionary variable to store VPP IPv6 addresses of the packet
        #  generator interfaces.
        ## @var vpp
        #  Test case object variable to store file descriptor of running vpp
        #  subprocess with open pipe to the standard error stream per
        #  VppTestCase object.

    ## Class method to do cleaning when all tests (test_) defined for
    #  VppTestCase class are finished.
    #  1. Terminate vpp and kill all vpp instances.
    #  2. Remove files from the shared memory.
    #  @param cls The class pointer.
    @classmethod
    def quit(cls):
        cls.vpp.terminate()
        cls.vpp = None
        os.system("rm -f /dev/shm/unittest-global_vm")
        os.system("rm -f /dev/shm/unittest-vpe-api")
        os.system("rm -f /dev/shm/unittest-db")

    ## Class method to define tear down action of the VppTestCase class.
    #  @param cls The class pointer.
    @classmethod
    def tearDownClass(cls):
        cls.quit()

    ## Method to define tear down VPP actions of the test case.
    #  @param self The object pointer.
    def tearDown(self):
        self.cli(2, "show int")
        self.cli(2, "show trace")
        self.cli(2, "show hardware")
        self.cli(2, "show ip arp")
        self.cli(2, "show ip fib")
        self.cli(2, "show error")
        self.cli(2, "show run")

    ## Method to define setup action of the test case.
    #  @param self The object pointer.
    def setUp(self):
        self.cli(2, "clear trace")

    ## Class method to print logs.
    #  Based on set level of verbosity print text in the terminal.
    #  @param cls The class pointer.
    #  @param s String variable to store text to be printed.
    #  @param v Integer variable to store required level of verbosity.
    @classmethod
    def log(cls, s, v=1):
        if cls.verbose >= v:
            print "LOG: " + LPURPLE + s + END

    ## Class method to execute api commands.
    #  Based on set level of verbosity print the output of the api command in
    #  the terminal.
    #  @param cls The class pointer.
    #  @param s String variable to store api command string.
    @classmethod
    def api(cls, s):
        p = subprocess.Popen(cls.vpp_api_test_cmdline,
                             stdout=subprocess.PIPE,
                             stdin=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        if cls.verbose > 0:
            print "API: " + RED + s + END
        p.stdin.write(s)
        out = p.communicate()[0]
        out = out.replace("vat# ", "", 2)
        if cls.verbose > 0:
            if len(out) > 1:
                print YELLOW + out + END
        ## @var p
        #  Object variable to store file descriptor of vpp_api_test subprocess
        #  with open pipes to the standard output, inputs and error streams.
        ## @var out
        #  Tuple variable to store standard output of vpp_api_test subprocess
        #  where the string "vat# " is replaced by empty string later.

    ## Class method to execute cli commands.
    #  Based on set level of verbosity of the log and verbosity defined by
    #  environmental variable execute the cli command and print the output in
    #  the terminal.
    #  CLI command is executed via vpp API test tool (exec + cli_command)
    #  @param cls The class pointer.
    #  @param v Integer variable to store required level of verbosity.
    #  @param s String variable to store cli command string.
    @classmethod
    def cli(cls, v, s):
        if cls.verbose < v:
            return
        p = subprocess.Popen(cls.vpp_api_test_cmdline,
                             stdout=subprocess.PIPE,
                             stdin=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        if cls.verbose > 0:
            print "CLI: " + RED + s + END
        p.stdin.write('exec ' + s)
        out = p.communicate()[0]
        out = out.replace("vat# ", "", 2)
        if cls.verbose > 0:
            if len(out) > 1:
                print YELLOW + out + END
        ## @var p
        #  Object variable to store file descriptor of vpp_api_test subprocess
        #  with open pipes to the standard output, inputs and error streams.
        ## @var out
        #  Tuple variable to store standard output of vpp_api_test subprocess
        #  where the string "vat# " is replaced by empty string later.

    ## Class method to create incoming packet stream for the packet-generator
    #  interface.
    #  Delete old /tmp/pgX_in.pcap file if exists and create the empty one and
    #  fill it with provided packets and add it to pg_streams list.
    #  @param cls The class pointer.
    #  @param i Integer variable to store the index of the packet-generator
    #  interface to create packet stream for.
    #  @param pkts List variable to store packets to be added to the stream.
    @classmethod
    def pg_add_stream(cls, i, pkts):
        os.system("rm -f /tmp/pg%u_in.pcap" % i)
        wrpcap("/tmp/pg%u_in.pcap" % i, pkts)
        # no equivalent API command
        cls.cli(0, "packet-generator new pcap /tmp/pg%u_in.pcap source pg%u"
                   " name pcap%u" % (i, i, i))
        cls.pg_streams.append('pcap%u' % i)

    ## Class method to enable packet capturing for the packet-generator
    #  interface.
    #  Delete old /tmp/pgX_out.pcap file if exists and set the packet-generator
    #  to capture outgoing packets to /tmp/pgX_out.pcap file.
    #  @param cls The class pointer.
    #  @param args List variable to store the indexes of the packet-generator
    #  interfaces to start packet capturing for.
    @classmethod
    def pg_enable_capture(cls, args):
        for i in args:
            os.system("rm -f /tmp/pg%u_out.pcap" % i)
            cls.cli(0, "packet-generator capture pg%u pcap /tmp/pg%u_out.pcap"
                    % (i, i))

    ## Class method to start packet sending.
    #  Start to send packets for all defined pg streams. Delete every stream
    #  from the stream list when sent and clear the pg_streams list.
    #  @param cls The class pointer.
    @classmethod
    def pg_start(cls):
        cls.cli(2, "trace add pg-input 50")  # 50 is maximum
        cls.cli(0, 'packet-generator enable')
        for stream in cls.pg_streams:
            cls.cli(0, 'packet-generator delete %s' % stream)
        cls.pg_streams = []

    ## Class method to return captured packets.
    #  Return packet captured for the defined packet-generator interface. Open
    #  the corresponding pcap file (/tmp/pgX_out.pcap), read the content and
    #  store captured packets to output variable.
    #  @param cls The class pointer.
    #  @param o Integer variable to store the index of the packet-generator
    #  interface.
    #  @return output List of packets captured on the defined packet-generator
    #  interface. If the corresponding pcap file (/tmp/pgX_out.pcap) does not
    #  exist return empty list.
    @classmethod
    def pg_get_capture(cls, o):
        pcap_filename = "/tmp/pg%u_out.pcap" % o
        try:
            output = rdpcap(pcap_filename)
        except IOError:  # TODO
            cls.log("WARNING: File %s does not exist, probably because no"
                    " packets arrived" % pcap_filename)
            return []
        return output
        ## @var pcap_filename
        #  File descriptor to the corresponding pcap file.

    ## Class method to create packet-generator interfaces.
    #  Create packet-generator interfaces and add host MAC addresses connected
    #  to these packet-generator interfaces to the MY_MACS dictionary.
    #  @param cls The class pointer.
    #  @param args List variable to store the indexes of the packet-generator
    #  interfaces to be created.
    @classmethod
    def create_interfaces(cls, args):
        for i in args:
            cls.MY_MACS[i] = "02:00:00:00:ff:%02x" % i
            cls.log("My MAC address is %s" % (cls.MY_MACS[i]))
            cls.api("pg_create_interface if_id %u" % i)
            cls.api("sw_interface_set_flags pg%u admin-up" % i)

    ## Static method to extend packet to specified size
    #  Extend provided packet to the specified size (including Ethernet FCS).
    #  The packet is extended by adding corresponding number of spaces to the
    #  packet payload.
    #  NOTE: Currently works only when Raw layer is present.
    #  @param packet Variable to store packet object.
    #  @param size Integer variable to store the required size of the packet.
    @staticmethod
    def extend_packet(packet, size):
        packet_len = len(packet) + 4
        extend = size - packet_len
        if extend > 0:
            packet[Raw].load += ' ' * extend
        ## @var packet_len
        #  Integer variable to store the current packet length including
        #  Ethernet FCS.
        ## @var extend
        #  Integer variable to store the size of the packet extension.

    ## Method to add packet info object to the packet_infos list.
    #  Extend the existing packet_infos list with the given information from
    #  the packet.
    #  @param self The object pointer.
    #  @param info Object to store required information from the packet.
    def add_packet_info_to_list(self, info):
        info.index = len(self.packet_infos)
        self.packet_infos[info.index] = info
        ## @var info.index
        # Info object attribute to store the packet order in the stream.
        ## @var packet_infos
        #  List variable to store required information from packets.

    ## Method to create packet info object.
    #  Create the existing packet_infos list with the given information from
    #  the packet.
    #  @param self The object pointer.
    #  @param pg_id Integer variable to store the index of the packet-generator
    #  interface.
    def create_packet_info(self, pg_id, target_id):
        info = _PacketInfo()
        self.add_packet_info_to_list(info)
        info.src = pg_id
        info.dst = target_id
        return info
        ## @var info
        #  Object to store required information from packet.
        ## @var info.src
        #  Info object attribute to store the index of the source packet
        #  generator interface of the packet.
        ## @var info.dst
        #  Info object attribute to store the index of the destination packet
        #  generator interface of the packet.

    ## Static method to return packet info string.
    #  Create packet info string from the provided info object that will be put
    #  to the packet payload.
    #  @param info Object to store required information from the packet.
    #  @return String of information about packet's order in the stream, source
    #  and destination packet generator interface.
    @staticmethod
    def info_to_payload(info):
        return "%d %d %d" % (info.index, info.src, info.dst)

    ## Static method to create packet info object from the packet payload.
    #  Create packet info object and set its attribute values based on data
    #  gained from the packet payload.
    #  @param payload String variable to store packet payload.
    #  @return info Object to store required information about the packet.
    @staticmethod
    def payload_to_info(payload):
        numbers = payload.split()
        info = _PacketInfo()
        info.index = int(numbers[0])
        info.src = int(numbers[1])
        info.dst = int(numbers[2])
        return info
        ## @var info.index
        #  Info object attribute to store the packet order in the stream.
        ## @var info.src
        #  Info object attribute to store the index of the source packet
        #  generator interface of the packet.
        ## @var info.dst
        #  Info object attribute to store the index of the destination packet
        #  generator interface of the packet.

    ## Method to return packet info object of the next packet in
    #  the packet_infos list.
    #  Get the next packet info object from the packet_infos list by increasing
    #  the packet_infos list index by one.
    #  @param self The object pointer.
    #  @param info Object to store required information about the packet.
    #  @return packet_infos[next_index] Next info object from the packet_infos
    #  list with stored information about packets. Return None if the end of
    #  the list is reached.
    def get_next_packet_info(self, info):
        if info is None:
            next_index = 0
        else:
            next_index = info.index + 1
        if next_index == len(self.packet_infos):
            return None
        else:
            return self.packet_infos[next_index]
        ## @var next_index
        #  Integer variable to store the index of the next info object.

    ## Method to return packet info object of the next packet with the required
    #  source packet generator interface.
    #  Iterate over the packet_infos list and search for the next packet info
    #  object with the required source packet generator interface.
    #  @param self The object pointer.
    #  @param src_pg Integer variable to store index of requested source packet
    #  generator interface.
    #  @param info Object to store required information about the packet.
    #  @return packet_infos[next_index] Next info object from the packet_infos
    #  list with stored information about packets. Return None if the end of
    #  the list is reached.
    def get_next_packet_info_for_interface(self, src_pg, info):
        while True:
            info = self.get_next_packet_info(info)
            if info is None:
                return None
            if info.src == src_pg:
                return info
        ## @var info.src
        #  Info object attribute to store the index of the source packet
        #  generator interface of the packet.

    ## Method to return packet info object of the next packet with required
    #  source and destination packet generator interfaces.
    #  Search for the next packet info object with the required source and
    #  destination packet generator interfaces.
    #  @param self The object pointer.
    #  @param src_pg Integer variable to store the index of the requested source
    #  packet generator interface.
    #  @param dst_pg Integer variable to store the index of the requested source
    #  packet generator interface.
    #  @param info Object to store required information about the packet.
    #  @return info Object with the info about the next packet with with
    #  required source and destination packet generator interfaces. Return None
    #  if there is no other packet with required data.
    def get_next_packet_info_for_interface2(self, src_pg, dst_pg, info):
        while True:
            info = self.get_next_packet_info_for_interface(src_pg, info)
            if info is None:
                return None
            if info.dst == dst_pg:
                return info
        ## @var info.dst
        #  Info object attribute to store the index of the destination packet
        #  generator interface of the packet.


## Subclass of the python unittest.TestResult class.
#
#  This subclass provides methods to compile information about which tests have
#  succeeded and which have failed.
class VppTestResult(unittest.TestResult):
    ## The constructor.
    #  @param stream File descriptor to store where to report test results. Set
    #  to the standard error stream by default.
    #  @param descriptions Boolean variable to store information if to use test
    #  case descriptions.
    #  @param verbosity Integer variable to store required verbosity level.
    def __init__(self, stream, descriptions, verbosity):
        unittest.TestResult.__init__(self, stream, descriptions, verbosity)
        self.stream = stream
        self.descriptions = descriptions
        self.verbosity = verbosity
        self.result_string = None
        ## @var result_string
        #  String variable to store the test case result string.


    ## Method called when the test case succeeds.
    #  Run the default implementation (that does nothing) and set the result
    #  string in case of test case success.
    #  @param self The object pointer.
    #  @param test Object variable to store the test case instance.
    def addSuccess(self, test):
        unittest.TestResult.addSuccess(self, test)
        self.result_string = GREEN + "OK" + END
        ## @var result_string
        #  String variable to store the test case result string.

    ## Method called when the test case signals a failure.
    #  Run the default implementation that appends a tuple (test, formatted_err)
    #  to the instance's failures attribute, where formatted_err is a formatted
    #  traceback derived from err and set the result string in case of test case
    #  success.
    #  @param self The object pointer.
    #  @param test Object variable to store the test case instance.
    #  @param err Tuple variable to store the error data:
    #  (type, value, traceback).
    def addFailure(self, test, err):
        unittest.TestResult.addFailure(self, test, err)
        self.result_string = RED + "FAIL" + END
        ## @var result_string
        #  String variable to store the test case result string.

    ## Method called when the test case raises an unexpected exception.
    #  Run the default implementation that appends a tuple (test, formatted_err)
    #  to the instance's error attribute, where formatted_err is a formatted
    #  traceback derived from err and set the result string in case of test case
    #  unexpected failure.
    #  @param self The object pointer.
    #  @param test Object variable to store the test case instance.
    #  @param err Tuple variable to store the error data:
    #  (type, value, traceback).
    def addError(self, test, err):
        unittest.TestResult.addError(self, test, err)
        self.result_string = RED + "ERROR" + END
        ## @var result_string
        #  String variable to store the test case result string.

    ## Method to get the description of the test case.
    #  Used to get the description string from the test case object.
    #  @param self The object pointer.
    #  @param test Object variable to store the test case instance.
    #  @return String of the short description if exist otherwise return test
    #  case name string.
    def getDescription(self, test):
        # TODO: if none print warning not raise exception
        short_description = test.shortDescription()
        if self.descriptions and short_description:
            return short_description
        else:
            return str(test)
        ## @var short_description
        #  String variable to store the short description of the test case.

    ## Method called when the test case is about to be run.
    #  Run the default implementation and based on the set verbosity level write
    #  the starting string to the output stream.
    #  @param self The object pointer.
    #  @param test Object variable to store the test case instance.
    def startTest(self, test):
        unittest.TestResult.startTest(self, test)
        if self.verbosity > 0:
            self.stream.writeln("Starting " + self.getDescription(test) + " ...")
            self.stream.writeln("------------------------------------------------------------------")

    ## Method called after the test case has been executed.
    #  Run the default implementation and based on the set verbosity level write
    #  the result string to the output stream.
    #  @param self The object pointer.
    #  @param test Object variable to store the test case instance.
    def stopTest(self, test):
        unittest.TestResult.stopTest(self, test)
        if self.verbosity > 0:
            self.stream.writeln("------------------------------------------------------------------")
            self.stream.writeln("%-60s%s" % (self.getDescription(test), self.result_string))
            self.stream.writeln("------------------------------------------------------------------")
        else:
            self.stream.writeln("%-60s%s" % (self.getDescription(test), self.result_string))

    ## Method to write errors and failures information to the output stream.
    #  Write content of errors and failures lists to the output stream.
    #  @param self The object pointer.
    def printErrors(self):
        self.stream.writeln()
        self.printErrorList('ERROR', self.errors)
        self.printErrorList('FAIL', self.failures)
        ## @var errors
        #  List variable containing 2-tuples of TestCase instances and strings
        #  holding formatted tracebacks. Each tuple represents a test which
        #  raised an unexpected exception.
        ## @var failures
        #  List variable containing 2-tuples of TestCase instances and strings
        #  holding formatted tracebacks. Each tuple represents a test where
        #  a failure was explicitly signalled using the TestCase.assert*()
        #  methods.

    ## Method to write the error information to the output stream.
    #  Write content of error lists to the output stream together with error
    #  type and test case description.
    #  @param self The object pointer.
    #  @param flavour String variable to store error type.
    #  @param errors List variable to store 2-tuples of TestCase instances and
    #  strings holding formatted tracebacks.
    def printErrorList(self, flavour, errors):
        for test, err in errors:
            self.stream.writeln('=' * 70)
            self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
            self.stream.writeln('-' * 70)
            self.stream.writeln("%s" % err)
        ## @var test
        #  Object variable to store the test case instance.
        ## @var err
        #  String variable to store formatted tracebacks.


## Subclass of the python unittest.TextTestRunner class.
#
#  A basic test runner implementation which prints results on standard error.
class VppTestRunner(unittest.TextTestRunner):
    ##  Class object variable to store the results of a set of tests.
    resultclass = VppTestResult

    ## Method to run the test.
    #  Print debug message in the terminal and run the standard run() method
    #  of the test runner collecting the result into the test result object.
    #  @param self The object pointer.
    #  @param test Object variable to store the test case instance.
    #  @return Test result object of the VppTestRunner.
    def run(self, test):
        print "Running tests using custom test runner"  # debug message
        return super(VppTestRunner, self).run(test)