1#!/usr/bin/env python
2
3# Copyright (C) 2016 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17'''Main test suite execution script.'''
18import argparse
19import inspect
20import logging
21import os
22import signal
23import subprocess
24import sys
25import time
26import collections
27import xml.etree.ElementTree as ET
28
29from config import Config
30from tests.harness import util_constants
31from tests.harness.exception import TestSuiteException, FailFastException
32from tests.harness import UtilAndroid
33from tests.harness import UtilBundle
34from tests.harness import util_log
35from tests.harness.util_functions import load_py_module
36from tests.harness.decorators import deprecated
37
38# For some reason pylint is not able to understand the class returned by
39# from util_log.get_logger() and generates a lot of false warnings
40#pylint: disable=maybe-no-member
41
42EMU_PROC = None
43
44def _parse_args():
45    '''Parse the command line arguments.
46
47    Returns:
48        A namespace object that contains the options specified to run_tests on
49        the command line.
50    '''
51
52    parser = argparse.ArgumentParser(description='Run the test suite.')
53
54    parser.add_argument('--config', '-c',
55                        metavar='path',
56                        help='Path to a custom config file.')
57    parser.add_argument('--device', '-d',
58                        help='Specify the device id of the device to test on.')
59    parser.add_argument('--test', '-t',
60                        metavar='path',
61                        help='Specify a specific test to run.')
62    group = parser.add_mutually_exclusive_group()
63    group.add_argument('--wimpy', '-w',
64                        action='store_true',
65                        default=None,
66                        help='Test only a core subset of features.')
67    group.add_argument('--app-types',
68                        default=['java', 'cpp', 'jni'],
69                        nargs='*',
70                        help='Specify a list of Android app types against which'
71                             ' to run the tests',
72                        dest='bundle_types')
73    parser.add_argument('--install-only',
74                        action='store_true',
75                        default=False,
76                        help='It only runs the pre-run stage of the test suite.'
77                             ' It installs the required APKs but does not '
78                             'execute the tests.',
79                        dest='install_only')
80    parser.add_argument('--no-install', '-n',
81                        action='store_true',
82                        default=False,
83                        help='Stop the test suite installing apks to device.',
84                        dest='noinstall')
85    parser.add_argument('--no-uninstall',
86                        action='store_true',
87                        default=False,
88                        help='Stop the test suite uninstalling apks after '
89                             'completion.',
90                        dest='nouninstall')
91    parser.add_argument('--print-to-stdout',
92                        action='store_true',
93                        default=False,
94                        help='Print all logging information to standard out.',
95                        dest='print_to_stdout')
96    parser.add_argument('--verbose', '-v',
97                        action='store_true',
98                        default=None,
99                        help='Store extra info in the log.')
100    parser.add_argument('--fail-fast',
101                        action='store_true',
102                        default=False,
103                        help='Exit the test suite immediately on the first failure.')
104    parser.add_argument('--run-emu',
105                        action='store_true',
106                        default=None,
107                        help='Spawn an emulator and run the test suite on that.'
108                             ' Specify the emulator command line in the config'
109                             ' file or with -emu-cmd.',
110                        dest='run_emu')
111
112    # Get the properties of the Config class and add a command line argument
113    # for each.
114    this_module = sys.modules[__name__]
115    for member_name, member_obj in inspect.getmembers(Config):
116        if (inspect.isdatadescriptor(member_obj) and
117            member_name not in ['__weakref__', 'device', 'verbose']):
118
119            # List type properties can take one or more arguments
120            num_args = None
121            if (isinstance(member_obj, property)
122                and isinstance(member_obj.fget(Config), list)):
123                num_args = '+'
124
125            opt_name = member_name.replace('_', '-')
126
127            setattr(this_module, opt_name, '')
128
129            parser.add_argument('--' + opt_name,
130                                nargs=num_args,
131                                help=member_obj.__doc__,
132                                dest=member_name)
133
134    return parser.parse_args()
135
136
137def _choice(first_choice, second_choice):
138    '''Return first_choice if it is not None otherwise return second_choice.
139
140    Args:
141        first_choice: The first choice value.
142        second_choice: The alternative value.
143
144    Returns:
145        The first argument if it is not None, and the second otherwise.
146    '''
147    return first_choice if first_choice else second_choice
148
149
150class State(object):
151    '''This class manages all objects required by the test suite.'''
152
153    # pylint: disable=too-many-instance-attributes
154    # Since this is a state class many attributes are expected.
155
156    def __init__(self):
157        '''State constructor.
158
159        Raises:
160            TestSuiteException: When unable to load config file.
161
162            AssertionError: When assertions fail.
163        '''
164
165        # Parse the command line options
166        args = _parse_args()
167
168        # create a config instance
169        if args.config:
170            # use the user supplied
171            config = State.load_user_configuration(args.config)
172        else:
173            # use the default configuration
174            config = Config()
175
176        # save the test blacklist
177        self.blacklist = _choice(args.blacklist, config.blacklist)
178
179        # Allow any of the command line arguments to override the
180        # values in the config file.
181        self.adb_path = _choice(args.adb_path, config.adb_path)
182
183        self.host_port = int(_choice(args.host_port, config.host_port))
184
185        self.device = _choice(args.device, config.device)
186
187        self.user_specified_device = self.device
188
189        self.device_port = int(_choice(args.device_port, config.device_port))
190
191        self.lldb_server_path_device = _choice(args.lldb_server_path_device,
192                                               config.lldb_server_path_device)
193
194        self.lldb_server_path_host = _choice(args.lldb_server_path_host,
195                                             config.lldb_server_path_host)
196
197        self.aosp_product_path = _choice(args.aosp_product_path,
198                                         config.aosp_product_path)
199
200        self.log_file_path = _choice(args.log_file_path, config.log_file_path)
201
202        self.results_file_path = _choice(args.results_file_path,
203                                         config.results_file_path)
204
205        self.lldb_path = _choice(args.lldb_path, config.lldb_path)
206        self.print_to_stdout = args.print_to_stdout
207        self.verbose = _choice(args.verbose, config.verbose)
208        self.timeout = int(_choice(args.timeout, config.timeout))
209        self.emu_cmd = _choice(args.emu_cmd, config.emu_cmd)
210        self.run_emu = args.run_emu
211        self.wimpy = args.wimpy
212        self.bundle_types = args.bundle_types if not self.wimpy else ['java']
213        self.fail_fast = args.fail_fast
214
215        # validate the param "verbose"
216        if not isinstance(self.verbose, bool):
217            raise TestSuiteException('The parameter "verbose" should be a '
218                                     'boolean: {0}'.format(self.verbose))
219
220        # create result array
221        self.results = dict()
222        self.single_test = args.test
223
224        # initialise the logging facility
225        log_level = logging.INFO if not self.verbose else logging.DEBUG
226        util_log.initialise("driver",
227                            print_to_stdout=self.print_to_stdout,
228                            level=log_level,
229                            file_mode='w', # open for write
230                            file_path=self.log_file_path
231                            )
232        log = util_log.get_logger()
233
234        if self.run_emu and not self.emu_cmd:
235            log.TestSuiteException('Need to specify --emu-cmd (or specify a'
236                ' value in the config file) if using --run-emu.')
237
238        # create a results file
239        self.results_file = open(self.results_file_path, 'w')
240
241        # create an android helper object
242        self.android = UtilAndroid(self.adb_path,
243                                   self.lldb_server_path_device,
244                                   self.device)
245        assert self.android
246
247        # create a test bundle
248        self.bundle = UtilBundle(self.android,
249                                 self.aosp_product_path)
250        assert self.bundle
251
252        # save the no pushing option
253        assert isinstance(args.noinstall, bool)
254        self.noinstall = args.noinstall
255
256        assert isinstance(args.nouninstall, bool)
257        self.nouninstall = args.nouninstall
258
259        # install only option
260        assert type(args.install_only) is bool
261        self.install_only = args.install_only
262        if self.install_only:
263            log.log_and_print('Option --install-only set. The test APKs will '
264                              'be installed on the device but the tests will '
265                              'not be executed.')
266            if self.noinstall:
267                raise TestSuiteException('Conflicting options given: '
268                                         '--install-only and --no-install')
269
270        # TCP port modifier which is used to increment the port number used for
271        # each test case to avoid collisions.
272        self.port_mod = 0
273
274        # total number of test files that have been executed
275        self.test_count = 0
276
277    def get_android(self):
278        '''Return the android ADB helper instance.
279
280        Returns:
281            The android ADB helper, instance of UtilAndroid.
282        '''
283        assert self.android
284        return self.android
285
286    def get_bundle(self):
287        '''Return the test executable bundle.
288
289        Returns:
290            The test exectable collection, instance of UtilBundle.
291        '''
292        return self.bundle
293
294    def add_result(self, name, app_type, result):
295        '''Add a test result to the collection.
296
297        Args:
298            name: String name of the test that has executed.
299            app_type: type of app i.e. java, jni, or cpp
300            result: String result of the test, "pass", "fail", "error".
301        '''
302        key = (name, app_type)
303        assert key not in self.results
304        self.results[key] = result
305
306    def get_single_test(self):
307        '''Get the name of the single test to run.
308
309        Returns:
310            A string that is the name of the python file containing the test to
311            be run. If all tests are to be run this returns None.
312        '''
313        return self.single_test
314
315    @staticmethod
316    def load_user_configuration(path):
317        '''Load the test suite config from the give path.
318
319        Instantiate the Config class found in the module at the given path.
320        If no suitable class is available, it raises a TestSuiteException.
321
322        Args:
323            path: String location of the module.
324
325        Returns:
326            an instance of the Config class, defined in the module.
327
328        Raises:
329            TestSuiteException: when unable to import the module or when a
330                                subclass of Config is not found inside it.
331        '''
332
333        # load the module
334        config_module = load_py_module(path)
335        if not config_module:
336            raise TestSuiteException('Unable to import the module from "%s"'
337                                     % (path))
338
339        # look for a subclass of Config
340        for name, value in inspect.getmembers(config_module):
341            if (inspect.isclass(value)
342                and name != 'Config'
343                and issubclass(value, Config)):
344                # that's our candidate
345                return value()
346
347        # otherwise there are no valid candidates
348        raise TestSuiteException('The provided user configuration is not '
349                                 'valid. The module must define a subclass '
350                                 'of Config')
351
352
353def _kill_emulator():
354    ''' Kill the emulator process. '''
355    global EMU_PROC
356    if EMU_PROC:
357        try:
358            EMU_PROC.terminate()
359        except OSError:
360            # can't kill a dead proc
361            log = util_log.get_logger()
362            log.debug('Trying to kill an emulator but it is already dead.')
363
364
365def _check_emulator_terminated():
366    ''' Throw an exception if the emulator process has ended.
367
368    Raises:
369        TestSuiteException: If the emulator process has ended.
370    '''
371    global EMU_PROC
372    assert EMU_PROC
373    if EMU_PROC.poll():
374        stdout, stderr = EMU_PROC.communicate()
375        raise TestSuiteException('The emulator terminated with output:'
376            '\nstderr: {0}\nstdout: {1}.'.format(stderr, stdout))
377
378
379@deprecated()
380def _launch_emulator(state):
381    '''Launch the emulator and wait for it to boot.
382
383    Args:
384        emu_cmd: The command line to run the emulator.
385
386    Raises:
387        TestSuiteException: If an emulator already exists or the emulator
388                            process terminated before we could connect to it, or
389                            we failed to copy lldb-server to the emulator.
390    '''
391    global EMU_PROC
392    android = state.android
393    if state.user_specified_device:
394        if android.device_with_substring_exists(state.user_specified_device):
395            raise TestSuiteException(
396                'A device with name {0} already exists.',
397                state.user_specified_device)
398    else:
399        if android.device_with_substring_exists('emulator'):
400            raise TestSuiteException('An emulator already exists.')
401
402    assert state.emu_cmd
403    EMU_PROC = subprocess.Popen(state.emu_cmd.split(),
404                                stdout=None,
405                                stderr=subprocess.STDOUT)
406
407    log = util_log.get_logger()
408    log.info('Launching emulator with command line {0}'.format(state.emu_cmd))
409
410    tries_number = 180
411    tries = tries_number
412    found_device = False
413    while not found_device:
414        try:
415            android.validate_device(False, 'emulator')
416            found_device = True
417        except TestSuiteException as ex:
418            tries -= 1
419            if tries == 0:
420                # Avoid infinitely looping if the emulator won't boot
421                log.warning(
422                    'Giving up trying to validate device after {0} tries.'
423                    .format(tries_number))
424                raise ex
425            _check_emulator_terminated()
426            # wait a bit and try again, maybe it has now booted
427            time.sleep(10)
428
429    tries = 500
430    while not android.is_booted():
431        tries -= 1
432        if tries == 0:
433            # Avoid infinitely looping if the emulator won't boot
434            raise TestSuiteException('The emulator has failed to boot.')
435        _check_emulator_terminated()
436        time.sleep(5)
437
438    # Need to be root before we can push lldb-server
439    android.adb_root()
440    android.wait_for_device()
441
442    # Push the lldb-server executable to the device.
443    output = android.adb('push {0} {1}'.format(state.lldb_server_path_host,
444                                               state.lldb_server_path_device))
445
446    if 'failed to copy' in output or 'No such file or directory' in output:
447        raise TestSuiteException(
448            'unable to push lldb-server to the emulator: {0}.'
449            .format(output))
450
451    output = android.shell('chmod a+x {0}'
452                           .format(state.lldb_server_path_device))
453
454    if 'No such file or directory' in output:
455        raise TestSuiteException('Failed to copy lldb-server to the emulator.')
456
457
458def _restart_emulator(state):
459    '''Kill the emulator and start a new instance.
460
461    Args:
462        state: Test suite state collection, instance of State.
463    '''
464    _kill_emulator()
465    _launch_emulator(state)
466
467
468def _run_test(state, name, bundle_type):
469    '''Execute a single test case.
470
471    Args:
472        state: Test suite state collection, instance of State.
473        name: String file name of the test to execute.
474        bundle_type: string for the installed app type (cpp|jni|java)
475
476    Raises:
477        AssertionError: When assertion fails.
478    '''
479    assert isinstance(name, str)
480
481    try:
482        state.android.check_adb_alive()
483    except TestSuiteException as expt:
484        global EMU_PROC
485        if EMU_PROC:
486            _restart_emulator(state)
487        else:
488            raise expt
489
490    log = util_log.get_logger()
491    sys.stdout.write('Running {0}\r'.format(name))
492    sys.stdout.flush()
493    log.info('Running {0}'.format(name))
494
495    run_tests_dir = os.path.dirname(os.path.realpath(__file__))
496    run_test_path = os.path.join(run_tests_dir, 'tests', 'run_test.py')
497
498    # Forward port for lldb-server on the device to our host
499    hport = int(state.host_port) + state.port_mod
500    dport = int(state.device_port) + state.port_mod
501    state.android.forward_port(hport, dport)
502    state.port_mod += 1
503
504    log.debug('Giving up control to {0}...'.format(name))
505
506    params = map(str, [
507        sys.executable,
508        run_test_path,
509        name,
510        state.log_file_path,
511        state.adb_path,
512        state.lldb_server_path_device,
513        state.aosp_product_path,
514        dport,
515        state.android.get_device_id(),
516        state.print_to_stdout,
517        state.verbose,
518        state.wimpy,
519        state.timeout,
520        bundle_type
521    ])
522
523    return_code = subprocess.call(params)
524    state.test_count += 1
525    state.android.remove_port_forwarding()
526    log.seek_to_end()
527
528    # report in sys.stdout the result
529    success = return_code == util_constants.RC_TEST_OK
530    status_handlers = collections.defaultdict(lambda: ('error', log.error), (
531            (util_constants.RC_TEST_OK, ('pass', log.info)),
532            (util_constants.RC_TEST_TIMEOUT, ('timeout', log.error)),
533            (util_constants.RC_TEST_IGNORED, ('ignored', log.info)),
534            (util_constants.RC_TEST_FAIL, ('fail', log.critical))
535        )
536    )
537    status_name, status_logger = status_handlers[return_code]
538    log.info('Running %s: %s', name, status_name.upper())
539    status_logger("Test %r: %s", name, status_name)
540
541    # Special case for ignored tests - just return now
542    if return_code == util_constants.RC_TEST_IGNORED:
543        return
544
545    state.add_result(name, bundle_type, status_name)
546
547    if state.fail_fast and not success:
548        raise FailFastException(name)
549
550    # print a running total pass rate
551    passes = sum(1 for key, value in state.results.items() if value == 'pass')
552    log.info('Current pass rate: %s of %s executed.', passes, len(state.results))
553
554
555def _check_lldbserver_exists(state):
556    '''Check lldb-server exists on the target device and it is executable.
557
558    Raises:
559        TestSuiteError: If lldb-server does not exist on the target.
560    '''
561    assert state
562
563    message = 'Unable to verify valid lldb-server on target'
564
565    android = state.get_android()
566    assert android
567
568    cmd = state.lldb_server_path_device
569    out = android.shell(cmd, False)
570    if not isinstance(out, str):
571        raise TestSuiteException(message)
572    if out.find('Usage:') < 0:
573        raise TestSuiteException(message)
574
575
576def _suite_pre_run(state):
577    '''This function is executed before the test cases are run (setup).
578
579    Args:
580        state: Test suite state collection, instance of State.
581
582    Return:
583        True if the pre_run step completes without error.
584        Checks made:
585            - Validating that adb exists and runs.
586            - Validating that a device is attached.
587            - We have root access to the device.
588            - All test binaries were pushed to the device.
589            - The port for lldb-server was forwarded correctly.
590
591    Raises:
592        AssertionError: When assertions fail.
593    '''
594    assert state
595    log = util_log.get_logger()
596
597    try:
598        android = state.get_android()
599        bundle = state.get_bundle()
600        assert android
601        assert bundle
602
603        # validate ADB helper class
604        android.validate_adb()
605        log.log_and_print('Located ADB')
606
607        if state.run_emu:
608            log.log_and_print('Launching emulator...')
609            _launch_emulator(state)
610            log.log_and_print('Started emulator ' + android.device)
611        else:
612            android.validate_device()
613            log.log_and_print('Located device ' + android.device)
614
615        if state.noinstall and not state.single_test:
616            bundle.check_apps_installed(state.wimpy)
617
618        # elevate to root user
619        android.adb_root()
620        android.wait_for_device()
621        # check that lldb-server exists on device
622        android.kill_servers()
623        _check_lldbserver_exists(state)
624
625        if not state.noinstall:
626            # push all tests to the device
627            log.log_and_print('Pushing all tests...')
628            bundle.push_all()
629            log.log_and_print('Pushed all tests')
630        log.log_and_print('Pre run complete')
631
632    except TestSuiteException as expt:
633        log.exception('Test suite pre run failure')
634
635        # Even if we are logging the error, it may be helpful and more
636        # immediate to find out the error into the terminal
637        log.log_and_print('ERROR: Unable to set up the test suite: %s\n'
638                          % expt.message, logging.ERROR)
639
640        return False
641    return True
642
643
644def _suite_post_run(state):
645    '''This function is executed after the test cases have run (teardown).
646
647    Args:
648        state: Test suite state collection, instance of State.
649    Returns:
650        Number of failures
651    '''
652    log = util_log.get_logger()
653
654    if not state.noinstall and not state.nouninstall:
655        if state.wimpy:
656            state.bundle.uninstall_all_apk()
657        else:
658            state.bundle.uninstall_all()
659        log.log_and_print('Uninstalled/Deleted all tests')
660
661    total = 0
662    passes = 0
663    failures = 0
664
665    results = ET.Element('testsuite')
666    results.attrib['name'] = 'LLDB RS Test Suite'
667
668    for key, value in state.results.items():
669        total += 1
670        if value == 'pass':
671            passes += 1
672        else:
673            failures += 1
674
675        # test case name, followed by pass, failure or error elements
676        testcase = ET.Element('testcase')
677        testcase.attrib['name'] = "%s:%s" % key
678        result_element = ET.Element(value)
679        result_element.text = "%s:%s" % key
680        testcase.append(result_element)
681        results.append(testcase)
682
683    assert passes + failures == total, 'Invalid test results status'
684    if failures:
685        log.log_and_print(
686            'The following failures occurred:\n%s\n' %
687            '\n'.join('failed: %s:%s' % test_spec
688                for test_spec, result in state.results.items() if result != 'pass'
689        ))
690
691    log.log_and_print('{0} of {1} passed'.format(passes, total))
692    if total:
693        log.log_and_print('{0}% rate'.format((passes*100)/total))
694
695    results.attrib['tests'] = str(total)
696    state.results_file.write(ET.tostring(results, encoding='iso-8859-1'))
697
698    return failures
699
700
701def _discover_tests(state):
702    '''Discover all tests in the tests directory.
703
704    Returns:
705        List of strings, test file names from the 'tests' directory.
706    '''
707    tests = []
708
709    single_test = state.get_single_test()
710    if single_test is None:
711        file_dir = os.path.dirname(os.path.realpath(__file__))
712        tests_dir = os.path.join(file_dir, 'tests')
713
714        for sub_dir in os.listdir(tests_dir):
715            current_test_dir = os.path.join(tests_dir, sub_dir)
716            if os.path.isdir(current_test_dir):
717                dir_name = os.path.basename(current_test_dir)
718
719                if dir_name == 'harness':
720                    continue
721
722                for item in os.listdir(current_test_dir):
723                    if (item.startswith('test')
724                        and item.endswith('.py')
725                        and not item in state.blacklist):
726                        tests.append(item)
727    else:
728        if single_test.endswith('.py'):
729            tests.append(single_test)
730        else:
731            tests.append(single_test + '.py')
732
733    return tests
734
735
736def _deduce_python_path(state):
737    '''Try to deduce the PYTHONPATH environment variable via the LLDB binary.
738
739    Args:
740        state: Test suite state collection, instance of State.
741
742    Returns:
743        True if PYTHONPATH has been updated, False otherwise.
744
745    Raises:
746        TestSuiteException: If lldb path provided in the config or command line
747                            is incorrect.
748        AssertionError: If an assertion fails.
749    '''
750
751    lldb_path = state.lldb_path
752    if not lldb_path:
753        # lldb may not be provided in preference of a manual $PYTHONPATH
754        return False
755
756    params = [lldb_path, '-P']
757
758    try:
759        proc = subprocess.Popen(params, stdout=subprocess.PIPE)
760    except OSError as err:
761        error_string = 'Could not run lldb at %s: %s' % (lldb_path, str(err))
762        raise TestSuiteException(error_string)
763
764    stdout = proc.communicate()[0]
765    if stdout:
766        os.environ['PYTHONPATH'] = stdout.strip()
767        return True
768
769    return False
770
771
772def main():
773    '''The lldb-renderscript test suite entry point.'''
774    log = None
775
776    try:
777        # parse the command line
778        state = State()
779        assert state
780
781        # logging is initialised in State()
782        log = util_log.get_logger()
783
784        # if we can, set PYTHONPATH for lldb bindings
785        if not _deduce_python_path(state):
786            log.log_and_print('Unable to deduce PYTHONPATH', logging.WARN)
787
788        # pre run step
789        if not _suite_pre_run(state):
790            raise TestSuiteException('Test suite pre-run step failed')
791        # discover all tests and execute them
792        tests = _discover_tests(state)
793        log.log_and_print('Found {0} tests'.format(len(tests)))
794        if state.install_only:
795            log.log_and_print('Test applications installed. Terminating due to '
796                              '--install-only option')
797        else:
798            # run the tests
799            for bundle_type in state.bundle_types:
800                log.info("Running bundle type '%s'", bundle_type)
801                for item in tests:
802                    _run_test(state, item, bundle_type)
803                # post run step
804            quit(0 if _suite_post_run(state) == 0 else 1)
805
806    except AssertionError:
807        if log:
808            log.exception('Internal test suite error')
809
810        print('Internal test suite error')
811        quit(1)
812
813    except FailFastException:
814        log.exception('Early exit after first test failure')
815        quit(1)
816
817    except TestSuiteException as error:
818        if log:
819            log.exception('Test suite exception')
820
821        print('{0}'.format(str(error)))
822        quit(2)
823
824    finally:
825        _kill_emulator()
826        logging.shutdown()
827
828def signal_handler(_, _unused):
829    '''Signal handler for SIGINT, caused by the user typing Ctrl-C.'''
830    # pylint: disable=unused-argument
831    # pylint: disable=protected-access
832    print('Ctrl+C!')
833    os._exit(1)
834
835
836# execution trampoline
837if __name__ == '__main__':
838    signal.signal(signal.SIGINT, signal_handler)
839    main()
840