summaryrefslogtreecommitdiff
path: root/subplot/vendored/daemon.py
diff options
context:
space:
mode:
Diffstat (limited to 'subplot/vendored/daemon.py')
-rw-r--r--subplot/vendored/daemon.py139
1 files changed, 131 insertions, 8 deletions
diff --git a/subplot/vendored/daemon.py b/subplot/vendored/daemon.py
index febf392..11f65bf 100644
--- a/subplot/vendored/daemon.py
+++ b/subplot/vendored/daemon.py
@@ -6,22 +6,63 @@ import subprocess
import time
+# A helper function for testing lib/daemon itself.
+def _daemon_shell_script(ctx, filename=None):
+ get_file = globals()["get_file"]
+ data = get_file(filename)
+ with open(filename, "wb") as f:
+ f.write(data)
+ os.chmod(filename, 0o755)
+
+
# Start a daemon that will open a port on localhost.
def daemon_start_on_port(ctx, path=None, args=None, name=None, port=None):
- daemon_start(ctx, path=path, args=args, name=name)
+ _daemon_start(ctx, path=path, args=args, name=name)
daemon_wait_for_port("localhost", port)
+# Start a daemon after a little wait. This is used only for testing the
+# port-waiting code.
+def _daemon_start_soonish(ctx, path=None, args=None, name=None, port=None):
+ _daemon_start(ctx, path=os.path.abspath(path), args=args, name=name)
+ daemon = ctx.declare("_daemon")
+
+ # Store the PID of the process we just started so that _daemon_stop_soonish
+ # can kill it during the cleanup phase. This works around the Subplot
+ # Python template not giving the step captures to cleanup functions. Note
+ # that this code assume at most one _soonish function is called.
+ daemon["_soonish"] = daemon[name]["pid"]
+
+ try:
+ daemon_wait_for_port("localhost", port)
+ except Exception as e:
+ daemon["_start_error"] = repr(e)
+
+ logging.info("pgrep: %r", _daemon_pgrep(path))
+
+
+def _daemon_stop_soonish(ctx, path=None, args=None, name=None, port=None):
+ ns = ctx.declare("_daemon")
+ pid = ns["_soonish"]
+ logging.debug(f"Stopping soonishly-started daemon, {pid}")
+ signo = signal.SIGKILL
+ try:
+ os.kill(pid, signo)
+ except ProcessLookupError:
+ logging.warning("Process did not actually exist (anymore?)")
+
+
# Start a daeamon, get its PID. Don't wait for a port or anything. This is
# meant for background processes that don't have port. Useful for testing the
# lib/daemon library of Subplot, but not much else.
-def daemon_start(ctx, path=None, args=None, name=None):
+def _daemon_start(ctx, path=None, args=None, name=None):
runcmd_run = globals()["runcmd_run"]
runcmd_exit_code_is = globals()["runcmd_exit_code_is"]
runcmd_get_exit_code = globals()["runcmd_get_exit_code"]
runcmd_get_stderr = globals()["runcmd_get_stderr"]
runcmd_prepend_to_path = globals()["runcmd_prepend_to_path"]
+ path = os.path.abspath(path)
argv = [path] + args.split()
logging.debug(f"Starting daemon {name}")
@@ -90,7 +131,7 @@ def _daemon_wait_for_pid(filename, timeout):
raise Exception("daemonize created a PID file without a PID")
-def daemon_wait_for_port(host, port, timeout=3.0):
+def daemon_wait_for_port(host, port, timeout=5.0):
addr = (host, port)
until = time.time() + timeout
while True:
@@ -99,23 +140,36 @@ def daemon_wait_for_port(host, port, timeout=3.0):
s.close()
return
except socket.timeout:
- logging.error(f"daemon did not respond at port {port} within {timeout} seconds")
+ logging.error(
+ f"daemon did not respond at port {port} within {timeout} seconds"
+ )
raise
except socket.error as e:
logging.info(f"could not connect to daemon at {port}: {e}")
pass
if time.time() >= until:
- logging.error(f"could not connect to daemon at {port} within {timeout} seconds")
+ logging.error(
+ f"could not connect to daemon at {port} within {timeout} seconds"
+ )
raise ConnectionRefusedError()
+ # Sleep a bit to avoid consuming too much CPU while busy-waiting.
+ time.sleep(0.1)
# Stop a daemon.
-def daemon_stop(ctx, name=None):
+def daemon_stop(ctx, path=None, args=None, name=None):
logging.debug(f"Stopping daemon {name}")
+
ns = ctx.declare("_daemon")
logging.debug(f" ns={ns}")
pid = ns[name]["pid"]
- signo = signal.SIGKILL
+ signo = signal.SIGTERM
+
+ this = ns[name]
+ data = open(this["stdout"]).read()
+ logging.debug(f"{name} stdout, before: {data!r}")
+ data = open(this["stderr"]).read()
+ logging.debug(f"{name} stderr, before: {data!r}")
logging.debug(f"Terminating process {pid} with signal {signo}")
try:
@@ -123,6 +177,20 @@ def daemon_stop(ctx, name=None):
except ProcessLookupError:
logging.warning("Process did not actually exist (anymore?)")
+ while True:
+ try:
+ os.kill(pid, 0)
+ logging.debug(f"Daemon {name}, pid {pid} still exists")
+ time.sleep(1)
+ except ProcessLookupError:
+ break
+ logging.debug(f"Daemon {name} is gone")
+
+ data = open(this["stdout"]).read()
+ logging.debug(f"{name} stdout, after: {data!r}")
+ data = open(this["stderr"]).read()
+ logging.debug(f"{name} stderr, after: {data!r}")
+
def daemon_no_such_process(ctx, args=None):
assert not _daemon_pgrep(args)
@@ -134,6 +202,61 @@ def daemon_process_exists(ctx, args=None):
def _daemon_pgrep(pattern):
logging.info(f"checking if process exists: pattern={pattern}")
- exit = subprocess.call(["pgrep", "-laf", pattern])
+ exit = subprocess.call(
+ ["pgrep", "-laf", pattern], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
+ )
logging.info(f"exit code: {exit}")
return exit == 0
+
+
+def daemon_start_fails_with(ctx, message=None):
+ daemon = ctx.declare("_daemon")
+ error = daemon["_start_error"]
+ logging.debug(f"daemon_start_fails_with: error={error!r}")
+ logging.debug(f"daemon_start_fails_with: message={message!r}")
+ assert message.lower() in error.lower()
+
+
+def daemon_get_stdout(ctx, name):
+ return _daemon_get_output(ctx, name, "stdout")
+
+
+def daemon_get_stderr(ctx, name):
+ return _daemon_get_output(ctx, name, "stderr")
+
+
+def _daemon_get_output(ctx, name, which):
+ ns = ctx.declare("_daemon")
+ this = ns[name]
+ filename = this[which]
+ data = open(filename).read()
+ logging.debug(f"Read {which} of daemon {name} from {filename}: {data!r}")
+ return data
+
+
+def daemon_has_produced_output(ctx, name=None):
+ started = time.time()
+ timeout = 5.0
+ while time.time() < started + timeout:
+ stdout = daemon_get_stdout(ctx, name)
+ stderr = daemon_get_stderr(ctx, name)
+ if stdout and stderr:
+ break
+ time.sleep(0.1)
+
+
+def daemon_stdout_is(ctx, name=None, text=None):
+ daemon_get_stdout = globals()["daemon_get_stdout"]
+ _daemon_output_is(ctx, name, text, daemon_get_stdout)
+
+
+def daemon_stderr_is(ctx, name=None, text=None):
+ daemon_get_stderr = globals()["daemon_get_stderr"]
+ _daemon_output_is(ctx, name, text, daemon_get_stderr)
+
+
+def _daemon_output_is(ctx, name, text, getter):
+ assert_eq = globals()["assert_eq"]
+ text = bytes(text, "UTF-8").decode("unicode_escape")
+ output = getter(ctx, name)
+ assert_eq(text, output)