llvm/lldb/test/API/test_runner/test/inferior.py

#!/usr/bin/env python
"""Inferior program used by process control tests."""

import argparse
import datetime
import signal
import subprocess
import sys
import time


def parse_args(command_line):
    """Parses the command line arguments given to it.

    @param command_line a list of command line arguments to be parsed.

    @return the argparse options dictionary.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--ignore-signal",
        "-i",
        dest="ignore_signals",
        metavar="SIGNUM",
        action="append",
        type=int,
        default=[],
        help="ignore the given signal number (if possible)",
    )
    parser.add_argument(
        "--launch-child-share-handles",
        action="store_true",
        help=(
            "launch a child inferior.py that shares stdout/stderr/stdio and "
            "never returns"
        ),
    )
    parser.add_argument(
        "--never-return",
        action="store_true",
        help="run in an infinite loop, never return",
    )
    parser.add_argument(
        "--return-code",
        "-r",
        type=int,
        default=0,
        help="specify the return code for the inferior upon exit",
    )
    parser.add_argument(
        "--sleep",
        "-s",
        metavar="SECONDS",
        dest="sleep_seconds",
        type=float,
        help="sleep for SECONDS seconds before returning",
    )
    parser.add_argument(
        "--verbose",
        "-v",
        action="store_true",
        help="log verbose operation details to stdout",
    )
    return parser.parse_args(command_line)


def handle_ignore_signals(options, signals):
    """Ignores any signals provided to it.

    @param options the command line options parsed by the program.
    General used to check flags for things like verbosity.

    @param signals the list of signals to ignore.  Can be None or zero-length.
    Entries should be type int.
    """
    if signals is None:
        return

    for signum in signals:
        if options.verbose:
            print("disabling signum {}".format(signum))
        signal.signal(signum, signal.SIG_IGN)


def handle_sleep(options, sleep_seconds):
    """Sleeps the number of seconds specified, restarting as needed.

    @param options the command line options parsed by the program.
    General used to check flags for things like verbosity.

    @param sleep_seconds the number of seconds to sleep.  If None
    or <= 0, no sleeping will occur.
    """
    if sleep_seconds is None:
        return

    if sleep_seconds <= 0:
        return

    end_time = datetime.datetime.now() + datetime.timedelta(0, sleep_seconds)
    if options.verbose:
        print("sleep end time: {}".format(end_time))

    # Do sleep in a loop: signals can interrupt.
    while datetime.datetime.now() < end_time:
        # We'll wrap this in a try/catch so we don't encounter
        # a race if a signal (ignored) knocks us out of this
        # loop and causes us to return.
        try:
            sleep_interval = end_time - datetime.datetime.now()
            sleep_seconds = sleep_interval.total_seconds()
            if sleep_seconds > 0:
                time.sleep(sleep_seconds)
        except:  # pylint: disable=bare-except
            pass


def handle_launch_children(options):
    if options.launch_child_share_handles:
        # Launch the child, share our file handles.
        # We won't bother reaping it since it will likely outlive us.
        subprocess.Popen([sys.executable, __file__, "--never-return"])


def handle_never_return(options):
    if not options.never_return:
        return

    # Loop forever.
    while True:
        try:
            time.sleep(10)
        except:  # pylint: disable=bare-except
            # Ignore
            pass


def main(command_line):
    """Drives the main operation of the inferior test program.

    @param command_line the command line options to process.

    @return the exit value (program return code) for the process.
    """
    options = parse_args(command_line)
    handle_ignore_signals(options, options.ignore_signals)
    handle_launch_children(options)
    handle_sleep(options, options.sleep_seconds)
    handle_never_return(options)

    return options.return_code


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))