diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-01-20 08:28:15 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-01-20 12:03:41 +0200 |
commit | da174232d510a343babb8efc2da8c5659acec25d (patch) | |
tree | d096e10cd9231adfa441b12234ffbd5e902b5319 /share | |
parent | 53c60f6387dff0d42eeb8079760790fdcea98984 (diff) | |
download | subplot-da174232d510a343babb8efc2da8c5659acec25d.tar.gz |
feat: lib/daemon.py can return daemon stdout/stderr content so far
Diffstat (limited to 'share')
-rw-r--r-- | share/python/lib/daemon.md | 35 | ||||
-rw-r--r-- | share/python/lib/daemon.py | 71 | ||||
-rw-r--r-- | share/python/lib/daemon.yaml | 10 |
3 files changed, 114 insertions, 2 deletions
diff --git a/share/python/lib/daemon.md b/share/python/lib/daemon.md index 4d91bd1..9484926 100644 --- a/share/python/lib/daemon.md +++ b/share/python/lib/daemon.md @@ -69,6 +69,41 @@ then there is no "/bin/sleep 12765" process ~~~ +# Daemon stdout and stderr are retrievable + +Sometimes it's useful for the step functions to be able to retrieve +the stdout or stderr of of the daemon, after it's started, or even +after it's terminated. This scenario verifies that `lib/daemon` can do +that. + +~~~scenario +given a daemon helper shell script chatty-daemon.sh +given there is no "chatty-daemon" process +when I start "./chatty-daemon.sh" as a background process as chatty-daemon +when daemon chatty-daemon has produced output +when I stop background process chatty-daemon +then there is no "chatty-daemon" process +then daemon chatty-daemon stdout is "hi there\n" +then daemon chatty-daemon stderr is "hola\n" +~~~ + +We make for the daemon to exit, to work around a race condition: if +the test program retrieves the daemon's output too fast, it may not +have had time to produce it yet. + + +~~~{#chatty-daemon.sh .file .sh .numberLines} +#!/bin/bash + +set -euo pipefail + +trap 'exit 0' TERM + +echo hola 1>&2 +echo hi there +~~~ + + --- title: Acceptance criteria for the lib/daemon Subplot library author: The Subplot project diff --git a/share/python/lib/daemon.py b/share/python/lib/daemon.py index 4ba1987..160da0a 100644 --- a/share/python/lib/daemon.py +++ b/share/python/lib/daemon.py @@ -62,6 +62,7 @@ def _daemon_start(ctx, path=None, args=None, name=None): 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}") @@ -154,10 +155,17 @@ def daemon_wait_for_port(host, port, timeout=5.0): # Stop a daemon. def daemon_stop(ctx, 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: @@ -165,6 +173,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) @@ -176,7 +198,7 @@ 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 @@ -187,3 +209,48 @@ def daemon_start_fails_with(ctx, message=None): 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) diff --git a/share/python/lib/daemon.yaml b/share/python/lib/daemon.yaml index f72ba1a..4fab1f6 100644 --- a/share/python/lib/daemon.yaml +++ b/share/python/lib/daemon.yaml @@ -17,6 +17,9 @@ - when: I stop background process {name} function: daemon_stop +- when: daemon {name} has produced output + function: daemon_has_produced_output + - then: a process "{args:text}" is running function: daemon_process_exists @@ -25,3 +28,10 @@ - then: starting daemon fails with "{message:text}" function: daemon_start_fails_with + +- then: daemon {name} stdout is "{text:text}" + function: daemon_stdout_is + +- then: daemon {name} stderr is "{text:text}" + function: daemon_stderr_is + |