trex_unit_test.py revision 8df11b2e
1#!/usr/bin/env python
2
3__copyright__ = "Copyright 2014"
4
5"""
6Name:
7     trex_unit_test.py
8
9
10Description:
11
12    This script creates the functionality to test the performance of the TRex traffic generator
13    The tested scenario is a TRex TG directly connected to a Cisco router.
14
15::
16
17    Topology:
18
19       -------                         --------
20      |       | Tx---1gig/10gig----Rx |        |
21      | TRex  |                       | router |
22      |       | Rx---1gig/10gig----Tx |        |
23       -------                         --------
24
25"""
26
27import os
28import sys
29import outer_packages
30import datetime
31import nose
32from nose.plugins import Plugin
33from nose.plugins.xunit import escape_cdata
34from nose.selector import Selector
35from nose.exc import SkipTest
36from nose.pyversion import force_unicode, format_exception
37import CustomLogger
38import misc_methods
39from rednose import RedNose
40import termstyle
41from trex import CTRexScenario
42from trex_stf_lib.trex_client import *
43from trex_stf_lib.trex_exceptions import *
44from trex_stl_lib.api import *
45from trex_stl_lib.utils.GAObjClass import GAmanager_Regression
46import trex_elk
47import trex
48import socket
49from pprint import pprint
50import time
51from distutils.dir_util import mkpath
52import re
53from io import StringIO
54
55
56
57TEST_ID = re.compile(r'^(.*?)(\(.*\))$')
58
59def id_split(idval):
60    m = TEST_ID.match(idval)
61    if m:
62        name, fargs = m.groups()
63        head, tail = name.rsplit(".", 1)
64        return [head, tail+fargs]
65    else:
66        return idval.rsplit(".", 1)
67
68# nose overrides
69
70# option to select wanted test by name without file, class etc.
71def new_Selector_wantMethod(self, method, orig_Selector_wantMethod = Selector.wantMethod):
72    result = orig_Selector_wantMethod(self, method)
73    return result and (not CTRexScenario.test or CTRexScenario.test in getattr(method, '__name__', ''))
74
75Selector.wantMethod = new_Selector_wantMethod
76
77def new_Selector_wantFunction(self, function, orig_Selector_wantFunction = Selector.wantFunction):
78    result = orig_Selector_wantFunction(self, function)
79    return result and (not CTRexScenario.test or CTRexScenario.test in getattr(function, '__name__', ''))
80
81Selector.wantFunction = new_Selector_wantFunction
82
83# override nose's strange representation of setUpClass errors
84def __suite_repr__(self):
85    if hasattr(self.context, '__module__'): # inside class, setUpClass etc.
86        class_repr = nose.suite._strclass(self.context)
87    else:                                   # outside of class, setUpModule etc.
88        class_repr = nose.suite._strclass(self.__class__)
89    return '%s.%s' % (class_repr, getattr(self.context, '__name__', self.context))
90
91nose.suite.ContextSuite.__repr__ = __suite_repr__
92nose.suite.ContextSuite.__str__  = __suite_repr__
93
94# /nose overrides
95
96def fatal(txt):
97    print(txt)
98    sys.exit(1)
99
100def check_trex_path(trex_path):
101    if os.path.isfile('%s/trex_daemon_server' % trex_path):
102        return os.path.abspath(trex_path)
103
104def check_setup_path(setup_path):
105    if os.path.isfile('%s/config.yaml' % setup_path):
106        return os.path.abspath(setup_path)
107
108
109def get_trex_path():
110    latest_build_path = check_trex_path(os.getenv('TREX_UNDER_TEST'))    # TREX_UNDER_TEST is env var pointing to <trex-core>/scripts
111    if not latest_build_path:
112        latest_build_path = check_trex_path(os.path.join(os.pardir, os.pardir))
113    if not latest_build_path:
114        fatal('Could not determine trex_under_test folder, try setting env.var. TREX_UNDER_TEST')
115    return latest_build_path
116
117
118def address_to_ip(address):
119    for i in range(5):
120        try:
121            return socket.gethostbyname(address)
122        except:
123            continue
124    return socket.gethostbyname(address)
125
126
127class TRexTee(object):
128    def __init__(self, encoding, *args):
129        self._encoding = encoding
130        self._streams = args
131
132    def write(self, data):
133        data = force_unicode(data, self._encoding)
134        for s in self._streams:
135            s.write(data)
136
137    def writelines(self, lines):
138        for line in lines:
139            self.write(line)
140
141    def flush(self):
142        for s in self._streams:
143            s.flush()
144
145    def isatty(self):
146        return False
147
148
149class CTRexTestConfiguringPlugin(Plugin):
150    encoding = 'UTF-8'
151
152    def __init__(self):
153        super(CTRexTestConfiguringPlugin, self).__init__()
154        self._capture_stack = []
155        self._currentStdout = None
156        self._currentStderr = None
157
158    def _timeTaken(self):
159        if hasattr(self, '_timer'):
160            taken = time.time() - self._timer
161        else:
162            # test died before it ran (probably error in setup())
163            # or success/failure added before test started probably
164            # due to custom TestResult munging
165            taken = 0.0
166        return taken
167
168    def _startCapture(self):
169        self._capture_stack.append((sys.stdout, sys.stderr))
170        self._currentStdout = StringIO()
171        self._currentStderr = StringIO()
172        sys.stdout = TRexTee(self.encoding, self._currentStdout, sys.stdout)
173        sys.stderr = TRexTee(self.encoding, self._currentStderr, sys.stderr)
174
175    def startContext(self, context):
176        self._startCapture()
177
178    def stopContext(self, context):
179        self._endCapture()
180
181    def beforeTest(self, test):
182        self._timer = time.time()
183        self._startCapture()
184
185    def _endCapture(self):
186        if self._capture_stack:
187            sys.stdout, sys.stderr = self._capture_stack.pop()
188
189    def afterTest(self, test):
190        self._endCapture()
191        self._currentStdout = None
192        self._currentStderr = None
193
194    def _getCapturedStdout(self):
195        if self._currentStdout:
196            value = self._currentStdout.getvalue()
197            if value:
198                return '<system-out><![CDATA[%s]]></system-out>' % escape_cdata(
199                        value)
200        return ''
201
202    def _getCapturedStderr(self):
203        if self._currentStderr:
204            value = self._currentStderr.getvalue()
205            if value:
206                return '<system-err><![CDATA[%s]]></system-err>' % escape_cdata(
207                        value)
208        return ''
209
210    def addError(self, test, err, capt=None):
211        elk = CTRexScenario.elk
212        if elk:
213            taken = self._timeTaken()
214            id = test.id()
215            err_msg=self._getCapturedStdout()+self._getCapturedStderr();
216            name=id_split(id)[-1]
217
218            elk_obj = trex.copy_elk_info ()
219            elk_obj['test']={
220                       "name"   : name,
221                       "name_key"   : name,
222                       "name_full"  : id,
223                       "type"  : self.get_operation_mode (),
224                       "duration_sec"  : taken,
225                       "result" :  "ERROR",
226                       "stdout" : err_msg,
227            };
228            #pprint(elk_obj['test']);
229            elk.reg.push_data(elk_obj)
230
231
232
233    def addFailure(self, test, err, capt=None, tb_info=None):
234        elk = CTRexScenario.elk
235        if elk:
236            taken = self._timeTaken()
237            tb = format_exception(err, self.encoding)
238            id = test.id()
239            err_msg=self._getCapturedStdout()+self._getCapturedStderr();
240            name=id_split(id)[-1]
241
242            elk_obj = trex.copy_elk_info ()
243            elk_obj['test']={
244                       "name"   : name,
245                       "name_key"   : name,
246                       "name_full"  : id,
247                        "type"  : self.get_operation_mode (),
248                        "duration_sec"  : taken,
249                        "result" :  "FAILURE",
250                        "stdout" : err_msg,
251            };
252            #pprint(elk_obj['test']);
253            elk.reg.push_data(elk_obj)
254
255
256
257    def addSuccess(self, test, capt=None):
258        elk = CTRexScenario.elk
259        if elk:
260            taken = self._timeTaken()
261            id = test.id()
262            name=id_split(id)[-1]
263            elk_obj = trex.copy_elk_info ()
264            elk_obj['test']={
265                       "name"   : name,
266                       "name_key"   : name,
267                       "name_full"  : id,
268                       "type"  : self.get_operation_mode (),
269                       "duration_sec"  : taken,
270                       "result" :  "PASS",
271                       "stdout" : "",
272            };
273            #pprint(elk_obj['test']);
274            elk.reg.push_data(elk_obj)
275
276
277
278    def get_operation_mode (self):
279        if self.stateful:
280            return('stateful');
281        return('stateless');
282
283
284
285
286##### option/configure
287
288    def options(self, parser, env = os.environ):
289        super(CTRexTestConfiguringPlugin, self).options(parser, env)
290        parser.add_option('--cfg', '--trex-scenario-config', action='store',
291                            dest='config_path',
292                            help='Specify path to folder with config.yaml and benchmark.yaml')
293        parser.add_option('--skip-clean', '--skip_clean', action='store_true',
294                            dest='skip_clean_config',
295                            help='Skip the clean configuration replace on the platform.')
296        parser.add_option('--load-image', '--load_image', action='store_true', default = False,
297                            dest='load_image',
298                            help='Install image specified in config file on router.')
299        parser.add_option('--log-path', '--log_path', action='store',
300                            dest='log_path',
301                            help='Specify path for the tests` log to be saved at. Once applied, logs capturing by nose will be disabled.') # Default is CURRENT/WORKING/PATH/trex_log/trex_log.log')
302        parser.add_option('--json-verbose', '--json_verbose', action="store_true", default = False,
303                            dest="json_verbose",
304                            help="Print JSON-RPC commands.")
305        parser.add_option('--telnet-verbose', '--telnet_verbose', action="store_true", default = False,
306                            dest="telnet_verbose",
307                            help="Print telnet commands and responces.")
308        parser.add_option('--server-logs', '--server_logs', action="store_true", default = False,
309                            dest="server_logs",
310                            help="Print server side (TRex and trex_daemon) logs per test.")
311        parser.add_option('--kill-running', '--kill_running', action="store_true", default = False,
312                            dest="kill_running",
313                            help="Kills running TRex process on remote server (useful for regression).")
314        parser.add_option('--func', '--functional', action="store_true", default = False,
315                            dest="functional",
316                            help="Run functional tests.")
317        parser.add_option('--stl', '--stateless', action="store_true", default = False,
318                            dest="stateless",
319                            help="Run stateless tests.")
320        parser.add_option('--stf', '--stateful', action="store_true", default = False,
321                            dest="stateful",
322                            help="Run stateful tests.")
323        parser.add_option('--pkg', type = str,
324                            help="Run with given TRex package. Make sure the path available at server machine. Implies --restart-daemon.")
325        parser.add_option('--restart-daemon', action="store_true", default = False,
326                            help="Flag that specifies to restart daemon. Implied by --pkg.")
327        parser.add_option('--collect', action="store_true", default = False,
328                            help="Alias to --collect-only.")
329        parser.add_option('--warmup', action="store_true", default = False,
330                            help="Warm up the system for stateful: run 30 seconds 9k imix test without check of results.")
331        parser.add_option('--test-client-package', '--test_client_package', action="store_true", default = False,
332                            help="Includes tests of client package.")
333        parser.add_option('--long', action="store_true", default = False,
334                            help="Flag of long tests (stability).")
335        parser.add_option('--ga', action="store_true", default = False,
336                            help="Flag to send benchmarks to GA.")
337        parser.add_option('--no-daemon', action="store_true", default = False,
338                            dest="no_daemon",
339                            help="Flag that specifies to use running stl server, no need daemons.")
340        parser.add_option('--debug-image', action="store_true", default = False,
341                            help="Flag that specifies to use t-rex-64-debug as TRex executable.")
342        parser.add_option('--trex-args', default = '',
343                            help="Additional TRex arguments (--no-watchdog etc.).")
344        parser.add_option('-t', '--test', type = str,
345                            help = 'Test name to run (without file, class etc.)')
346
347
348    def configure(self, options, conf):
349        self.collect_only   = options.collect_only
350        self.functional     = options.functional
351        self.stateless      = options.stateless
352        self.stateful       = options.stateful
353        self.pkg            = options.pkg
354        self.restart_daemon = options.restart_daemon
355        self.json_verbose   = options.json_verbose
356        self.telnet_verbose = options.telnet_verbose
357        self.no_daemon      = options.no_daemon
358        CTRexScenario.test  = options.test
359        if self.no_daemon and (self.pkg or self.restart_daemon):
360            fatal('You have specified both --no-daemon and either --pkg or --restart-daemon at same time.')
361        if self.no_daemon and self.stateful :
362            fatal("Can't run stateful without daemon.")
363        if self.collect_only or self.functional:
364            return
365        if CTRexScenario.setup_dir and options.config_path:
366            fatal('Please either define --cfg or use env. variable SETUP_DIR, not both.')
367        if not options.config_path and CTRexScenario.setup_dir:
368            options.config_path = CTRexScenario.setup_dir
369        if not options.config_path:
370            fatal('Please specify path to config.yaml using --cfg parameter or env. variable SETUP_DIR')
371        options.config_path = options.config_path.rstrip('/')
372        CTRexScenario.setup_name = os.path.basename(options.config_path)
373        self.configuration = misc_methods.load_complete_config_file(os.path.join(options.config_path, 'config.yaml'))
374        self.configuration.trex['trex_name'] = address_to_ip(self.configuration.trex['trex_name']) # translate hostname to ip
375        self.benchmark     = misc_methods.load_benchmark_config_file(os.path.join(options.config_path, 'benchmark.yaml'))
376        self.enabled       = True
377        self.modes         = self.configuration.trex.get('modes', [])
378        self.kill_running  = options.kill_running
379        self.load_image    = options.load_image
380        self.clean_config  = False if options.skip_clean_config else True
381        self.server_logs   = options.server_logs
382        if options.log_path:
383            self.loggerPath = options.log_path
384        # initialize CTRexScenario global testing class, to be used by all tests
385        CTRexScenario.configuration = self.configuration
386        CTRexScenario.no_daemon     = options.no_daemon
387        CTRexScenario.benchmark     = self.benchmark
388        CTRexScenario.modes         = set(self.modes)
389        CTRexScenario.server_logs   = self.server_logs
390        CTRexScenario.debug_image   = options.debug_image
391        CTRexScenario.json_verbose  = self.json_verbose
392        if not self.no_daemon:
393            CTRexScenario.trex      = CTRexClient(trex_host   = self.configuration.trex['trex_name'],
394                                                  verbose     = self.json_verbose,
395                                                  debug_image = options.debug_image,
396                                                  trex_args   = options.trex_args)
397
398        if self.pkg or self.restart_daemon:
399            if not CTRexScenario.trex.check_master_connectivity():
400                fatal('Could not connect to master daemon')
401        if options.ga and CTRexScenario.setup_name:
402            CTRexScenario.GAManager  = GAmanager_Regression(GoogleID         = 'UA-75220362-3',
403                                                            AnalyticsUserID  = CTRexScenario.setup_name,
404                                                            QueueSize        = 100,
405                                                            Timeout          = 3,  # seconds
406                                                            UserPermission   = 1,
407                                                            BlockingMode     = 0,
408                                                            appName          = 'TRex',
409                                                            appVer           = CTRexScenario.trex_version)
410
411            CTRexScenario.elk = trex_elk.TRexEs('sceasr-b20',9200);
412            self.set_cont_elk_info ()
413
414    def set_cont_elk_info (self):
415        elk_info={}
416        timestamp = datetime.datetime.now() - datetime.timedelta(hours=2); # need to update this
417        info = {};
418
419
420        img={}
421        img['sha'] = "v2.14"                #TBD
422        img['build_time'] = timestamp.strftime("%Y-%m-%d %H:%M:%S")
423        img['version'] = "v2.14"           #TBD need to fix
424        img['formal'] = False
425
426        setup={}
427
428        setup['distro']='None'            #TBD 'Ubunto14.03'
429        setup['kernel']='None'           #TBD '2.6.12'
430        setup['baremetal']=True          #TBD
431        setup['hypervisor']='None'       #TBD
432        setup['name']=CTRexScenario.setup_name
433
434        setup['cpu-sockets']=0           #TBD  2
435        setup['cores']=0                 #TBD 16
436        setup['cpu-speed']=-1            #TBD 3.5
437
438        setup['dut'] ='None'             #TBD 'loopback'
439        setup['drv-name']='None'         #TBD 'mlx5'
440        setup['nic-ports']=0             #TBD 2
441        setup['total-nic-ports']=0       #TBD 2
442        setup['nic-speed'] = "None"      #"40GbE" TBD
443
444
445
446        info['image'] = img
447        info['setup'] = setup
448
449        elk_info['info'] =info;
450
451        elk_info['timestamp']=timestamp.strftime("%Y-%m-%d %H:%M:%S")  # need to update it
452        elk_info['build_id']=os.environ.get('BUILD_ID')
453        elk_info['scenario']=os.environ.get('SCENARIO')
454
455        CTRexScenario.elk_info = elk_info
456
457
458    def begin (self):
459        client = CTRexScenario.trex
460        if self.pkg and not CTRexScenario.pkg_updated:
461            if client.master_daemon.is_trex_daemon_running() and client.get_trex_cmds() and not self.kill_running:
462                fatal("Can't update TRex, it's running. Consider adding --kill-running flag.")
463            print('Updating TRex to %s' % self.pkg)
464            if not client.master_daemon.update_trex(self.pkg):
465                fatal('Failed to update TRex.')
466            else:
467                print('Updated.')
468            CTRexScenario.pkg_updated = True
469        if self.functional or self.collect_only:
470            return
471        if self.pkg or self.restart_daemon:
472            print('Restarting TRex daemon server')
473            res = client.restart_trex_daemon()
474            if not res:
475                fatal('Could not restart TRex daemon server')
476            print('Restarted.')
477
478            if self.kill_running:
479                client.kill_all_trexes()
480            else:
481                if client.get_trex_cmds():
482                    fatal('TRex is already running')
483        if not self.no_daemon:
484            try:
485                client.check_server_connectivity()
486            except Exception as e:
487                fatal(e)
488
489
490        if 'loopback' not in self.modes:
491            CTRexScenario.router_cfg = dict(config_dict      = self.configuration.router,
492                                            forceImageReload = self.load_image,
493                                            silent_mode      = not self.telnet_verbose,
494                                            forceCleanConfig = self.clean_config,
495                                            tftp_config_dict = self.configuration.tftp)
496        try:
497            CustomLogger.setup_custom_logger('TRexLogger', self.loggerPath)
498        except AttributeError:
499            CustomLogger.setup_custom_logger('TRexLogger')
500
501    def finalize(self, result):
502        while self._capture_stack:
503            self._endCapture()
504
505        if self.functional or self.collect_only:
506            return
507        #CTRexScenario.is_init = False
508        if self.stateful:
509            CTRexScenario.trex = None
510        if self.stateless:
511            if self.no_daemon:
512                if CTRexScenario.stl_trex and CTRexScenario.stl_trex.is_connected():
513                    CTRexScenario.stl_trex.disconnect()
514            else:
515                CTRexScenario.trex.force_kill(False)
516            CTRexScenario.stl_trex = None
517
518
519def save_setup_info():
520    try:
521        if CTRexScenario.setup_name and CTRexScenario.trex_version:
522            setup_info = ''
523            for key, value in CTRexScenario.trex_version.items():
524                setup_info += '{0:8}: {1}\n'.format(key, value)
525            cfg = CTRexScenario.configuration
526            setup_info += 'Server: %s, Modes: %s' % (cfg.trex.get('trex_name'), cfg.trex.get('modes'))
527            if cfg.router:
528                setup_info += '\nRouter: Model: %s, Image: %s' % (cfg.router.get('model'), CTRexScenario.router_image)
529            if CTRexScenario.debug_image:
530                setup_info += '\nDebug image: %s' % CTRexScenario.debug_image
531
532            with open('%s/report_%s.info' % (CTRexScenario.report_dir, CTRexScenario.setup_name), 'w') as f:
533                f.write(setup_info)
534    except Exception as err:
535        print('Error saving setup info: %s ' % err)
536
537
538if __name__ == "__main__":
539
540    # setting defaults. By default we run all the test suite
541    specific_tests              = False
542    CTRexScenario.report_dir    = 'reports'
543    need_to_copy                = False
544    setup_dir                   = os.getenv('SETUP_DIR', '').rstrip('/')
545    CTRexScenario.setup_dir     = check_setup_path(setup_dir)
546    CTRexScenario.scripts_path  = get_trex_path()
547    if not CTRexScenario.setup_dir:
548        CTRexScenario.setup_dir = check_setup_path(os.path.join('setups', setup_dir))
549
550
551    nose_argv = ['', '-s', '-v', '--exe', '--rednose', '--detailed-errors']
552    test_client_package = False
553    if '--test-client-package' in sys.argv:
554        test_client_package = True
555
556    if '--collect' in sys.argv:
557        sys.argv.append('--collect-only')
558    if '--collect-only' in sys.argv: # this is a user trying simply to view the available tests. no need xunit.
559        CTRexScenario.is_test_list   = True
560        xml_arg                      = ''
561    else:
562        xml_name                     = 'unit_test.xml'
563        if CTRexScenario.setup_dir:
564            CTRexScenario.setup_name = os.path.basename(CTRexScenario.setup_dir)
565            xml_name = 'report_%s.xml' % CTRexScenario.setup_name
566        xml_arg= '--xunit-file=%s/%s' % (CTRexScenario.report_dir, xml_name)
567        mkpath(CTRexScenario.report_dir)
568
569    sys_args = sys.argv[:]
570    for i, arg in enumerate(sys.argv):
571        if 'log-path' in arg:
572            nose_argv += ['--nologcapture']
573        else:
574            for tests_type in CTRexScenario.test_types.keys():
575                if tests_type in arg:
576                    specific_tests = True
577                    CTRexScenario.test_types[tests_type].append(arg[arg.find(tests_type):])
578                    sys_args.remove(arg)
579
580    if not specific_tests:
581        for key in ('--func', '--functional'):
582            if key in sys_args:
583                CTRexScenario.test_types['functional_tests'].append('functional_tests')
584                sys_args.remove(key)
585        for key in ('--stf', '--stateful'):
586            if key in sys_args:
587                CTRexScenario.test_types['stateful_tests'].append('stateful_tests')
588                sys_args.remove(key)
589        for key in ('--stl', '--stateless'):
590            if key in sys_args:
591                CTRexScenario.test_types['stateless_tests'].append('stateless_tests')
592                sys_args.remove(key)
593        # Run all of the tests or just the selected ones
594        if not sum([len(x) for x in CTRexScenario.test_types.values()]):
595            for key in CTRexScenario.test_types.keys():
596                CTRexScenario.test_types[key].append(key)
597
598    nose_argv += sys_args
599
600    addplugins = [RedNose(), CTRexTestConfiguringPlugin()]
601    result = True
602    try:
603        if len(CTRexScenario.test_types['functional_tests']):
604            additional_args = ['--func'] + CTRexScenario.test_types['functional_tests']
605            if xml_arg:
606                additional_args += ['--with-xunit', xml_arg.replace('.xml', '_functional.xml')]
607            result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins)
608        if len(CTRexScenario.test_types['stateful_tests']):
609            additional_args = ['--stf']
610            if '--warmup' in sys.argv:
611                additional_args.append('stateful_tests/trex_imix_test.py:CTRexIMIX_Test.test_warm_up')
612            additional_args += CTRexScenario.test_types['stateful_tests']
613            if not test_client_package:
614                additional_args.extend(['-a', '!client_package'])
615            if xml_arg:
616                additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateful.xml')]
617            result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) and result
618        if len(CTRexScenario.test_types['stateless_tests']):
619            additional_args = ['--stl', 'stateless_tests/stl_general_test.py:STLBasic_Test.test_connectivity'] + CTRexScenario.test_types['stateless_tests']
620            if not test_client_package:
621                additional_args.extend(['-a', '!client_package'])
622            if xml_arg:
623                additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateless.xml')]
624            result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) and result
625    #except Exception as e:
626    #    result = False
627    #    print(e)
628    finally:
629        save_setup_info()
630
631    if not CTRexScenario.is_test_list:
632        if result == True:
633            print(termstyle.green("""
634                 ..::''''::..
635               .;''        ``;.
636              ::    ::  ::    ::
637             ::     ::  ::     ::
638             ::     ::  ::     ::
639             :: .:' ::  :: `:. ::
640             ::  :          :  ::
641              :: `:.      .:' ::
642               `;..``::::''..;'
643                 ``::,,,,::''
644
645               ___  ___   __________
646              / _ \/ _ | / __/ __/ /
647             / ___/ __ |_\ \_\ \/_/
648            /_/  /_/ |_/___/___(_)
649
650        """))
651            sys.exit(0)
652        else:
653            print(termstyle.red("""
654           /\_/\
655          ( o.o )
656           > ^ <
657
658This cat is sad, test failed.
659        """))
660            sys.exit(-1)
661
662
663
664
665
666
667
668
669
670
671
672