1#!/usr/bin/env python
2"""Inferior program used by process control tests."""
3
4from __future__ import print_function
5
6import argparse
7import datetime
8import signal
9import subprocess
10import sys
11import time
12
13
14def parse_args(command_line):
15    """Parses the command line arguments given to it.
16
17    @param command_line a list of command line arguments to be parsed.
18
19    @return the argparse options dictionary.
20    """
21    parser = argparse.ArgumentParser()
22    parser.add_argument(
23        "--ignore-signal",
24        "-i",
25        dest="ignore_signals",
26        metavar="SIGNUM",
27        action="append",
28        type=int,
29        default=[],
30        help="ignore the given signal number (if possible)")
31    parser.add_argument(
32        "--launch-child-share-handles",
33        action="store_true",
34        help=("launch a child inferior.py that shares stdout/stderr/stdio and "
35              "never returns"))
36    parser.add_argument(
37        "--never-return",
38        action="store_true",
39        help="run in an infinite loop, never return")
40    parser.add_argument(
41        "--return-code",
42        "-r",
43        type=int,
44        default=0,
45        help="specify the return code for the inferior upon exit")
46    parser.add_argument(
47        "--sleep",
48        "-s",
49        metavar="SECONDS",
50        dest="sleep_seconds",
51        type=float,
52        help="sleep for SECONDS seconds before returning")
53    parser.add_argument(
54        "--verbose", "-v", action="store_true",
55        help="log verbose operation details to stdout")
56    return parser.parse_args(command_line)
57
58
59def handle_ignore_signals(options, signals):
60    """Ignores any signals provided to it.
61
62    @param options the command line options parsed by the program.
63    General used to check flags for things like verbosity.
64
65    @param signals the list of signals to ignore.  Can be None or zero-length.
66    Entries should be type int.
67    """
68    if signals is None:
69        return
70
71    for signum in signals:
72        if options.verbose:
73            print("disabling signum {}".format(signum))
74        signal.signal(signum, signal.SIG_IGN)
75
76
77def handle_sleep(options, sleep_seconds):
78    """Sleeps the number of seconds specified, restarting as needed.
79
80    @param options the command line options parsed by the program.
81    General used to check flags for things like verbosity.
82
83    @param sleep_seconds the number of seconds to sleep.  If None
84    or <= 0, no sleeping will occur.
85    """
86    if sleep_seconds is None:
87        return
88
89    if sleep_seconds <= 0:
90        return
91
92    end_time = datetime.datetime.now() + datetime.timedelta(0, sleep_seconds)
93    if options.verbose:
94        print("sleep end time: {}".format(end_time))
95
96    # Do sleep in a loop: signals can interrupt.
97    while datetime.datetime.now() < end_time:
98        # We'll wrap this in a try/catch so we don't encounter
99        # a race if a signal (ignored) knocks us out of this
100        # loop and causes us to return.
101        try:
102            sleep_interval = end_time - datetime.datetime.now()
103            sleep_seconds = sleep_interval.total_seconds()
104            if sleep_seconds > 0:
105                time.sleep(sleep_seconds)
106        except:  # pylint: disable=bare-except
107            pass
108
109
110def handle_launch_children(options):
111    if options.launch_child_share_handles:
112        # Launch the child, share our file handles.
113        # We won't bother reaping it since it will likely outlive us.
114        subprocess.Popen([sys.executable, __file__, "--never-return"])
115
116
117def handle_never_return(options):
118    if not options.never_return:
119        return
120
121    # Loop forever.
122    while True:
123        try:
124            time.sleep(10)
125        except:  # pylint: disable=bare-except
126            # Ignore
127            pass
128
129
130def main(command_line):
131    """Drives the main operation of the inferior test program.
132
133    @param command_line the command line options to process.
134
135    @return the exit value (program return code) for the process.
136    """
137    options = parse_args(command_line)
138    handle_ignore_signals(options, options.ignore_signals)
139    handle_launch_children(options)
140    handle_sleep(options, options.sleep_seconds)
141    handle_never_return(options)
142
143    return options.return_code
144
145if __name__ == "__main__":
146    sys.exit(main(sys.argv[1:]))
147