summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcheck5
-rw-r--r--ewww.md12
-rw-r--r--runcmd.py73
-rw-r--r--runcmd.yaml13
-rw-r--r--subplot/daemon.py (renamed from daemon.py)7
-rw-r--r--subplot/ewww.py (renamed from ewww.py)10
-rw-r--r--subplot/ewww.yaml (renamed from ewww.yaml)0
-rw-r--r--subplot/http.py (renamed from http.py)38
-rw-r--r--subplot/runcmd.py243
-rw-r--r--subplot/runcmd.yaml83
10 files changed, 365 insertions, 119 deletions
diff --git a/check b/check
index deda000..53b1ee3 100755
--- a/check
+++ b/check
@@ -51,7 +51,10 @@ then
cargo clippy $quiet
fi
$hideok cargo test $quiet
-$hideok cargo fmt -- --check
+if cargo fmt --help > /dev/null 2> /dev/null
+then
+ $hideok cargo fmt -- --check
+fi
$hideok ./mktestcert test.key test.pem hunter2
diff --git a/ewww.md b/ewww.md
index f2d1778..9b08a97 100644
--- a/ewww.md
+++ b/ewww.md
@@ -144,13 +144,13 @@ and allow is "GET HEAD"
title: "Ewww — a Web server for static sites"
author: Lars Wirzenius
bindings:
- - ewww.yaml
- - runcmd.yaml
+ - subplot/ewww.yaml
+ - subplot/runcmd.yaml
functions:
- - ewww.py
- - daemon.py
- - http.py
- - runcmd.py
+ - subplot/ewww.py
+ - subplot/daemon.py
+ - subplot/http.py
+ - subplot/runcmd.py
classes:
- scenario-disabled
...
diff --git a/runcmd.py b/runcmd.py
deleted file mode 100644
index 631d8c0..0000000
--- a/runcmd.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Some step implementations for running commands and capturing the result.
-
-import logging
-import os
-import subprocess
-
-
-# Run a command, capture its stdout, stderr, and exit code in context.
-def runcmd(ctx, argv, **kwargs):
- logging.debug(f"Running program")
- logging.debug(f" cwd={os.getcwd()}")
- logging.debug(f" argv={argv}")
- logging.debug(f" kwargs={argv}")
- p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
- stdout, stderr = p.communicate("")
- ctx["argv"] = argv
- ctx["stdout"] = stdout.decode("utf-8")
- ctx["stderr"] = stderr.decode("utf-8")
- ctx["exit"] = p.returncode
-
-
-# Check that latest exit code captured by runcmd was a specific one.
-def exit_code_is(ctx, wanted):
- logging.debug(f"Verifying exit code is {wanted} (it is {ctx.get('exit')})")
- assert_eq(ctx.get("exit"), wanted)
-
-
-# Check that latest exit code captured by runcmd was not a specific one.
-def exit_code_is_not(ctx, unwanted):
- logging.debug(f"Verifying exit code is NOT {unwanted} (it is {ctx.get('exit')})")
- assert_ne(ctx.get("exit"), wanted)
-
-
-# Check that latest exit code captured by runcmd was zero.
-def exit_code_zero(ctx):
- exit_code_is(ctx, 0)
-
-
-# Check that latest exit code captured by runcmd was not zero.
-def exit_code_nonzero(ctx):
- exit_code_is_not(ctx, 0)
-
-
-# Check that stdout of latest runcmd contains a specific string.
-def stdout_contains(ctx, pattern=None):
- logging.debug(f"Verifying stdout contains {pattern}")
- logging.debug(f" stdout is {ctx.get('stdout', '')})")
- stdout = ctx.get("stdout", "")
- assert_eq(pattern in stdout, True)
-
-
-# Check that stdout of latest runcmd does not contain a specific string.
-def stdout_does_not_contain(ctx, pattern=None):
- logging.debug(f"Verifying stdout does NOT contain {pattern}")
- logging.debug(f" stdout is {ctx.get('stdout', '')})")
- stdout = ctx.get("stdout", "")
- assert_eq(pattern not in stdout, True)
-
-
-# Check that stderr of latest runcmd does contains a specific string.
-def stderr_contains(ctx, pattern=None):
- logging.debug(f"Verifying stderr contains {pattern}")
- logging.debug(f" stderr is {ctx.get('stderr', '')})")
- stderr = ctx.get("stderr", "")
- assert_eq(pattern in stderr, True)
-
-
-# Check that stderr of latest runcmd does not contain a specific string.
-def stderr_does_not_contain(ctx, pattern=None):
- logging.debug(f"Verifying stderr does NOT contain {pattern}")
- logging.debug(f" stderr is {ctx.get('stderr', '')})")
- stderr = ctx.get("stderr", "")
- assert_eq(pattern not in stderr, True)
diff --git a/runcmd.yaml b/runcmd.yaml
deleted file mode 100644
index 02e5ee1..0000000
--- a/runcmd.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-- then: exit code is non-zero
- function: exit_code_nonzero
-
-- then: output matches /(?P<pattern>.+)/
- function: stdout_contains
- regex: true
-
-- then: stderr matches /(?P<pattern>.+)/
- function: stderr_contains
- regex: true
-
-- then: program finished successfully
- function: exit_code_zero
diff --git a/daemon.py b/subplot/daemon.py
index 585fe5a..00c9d2d 100644
--- a/daemon.py
+++ b/subplot/daemon.py
@@ -9,6 +9,9 @@ import signal
# Start a process in the background.
def start_daemon(ctx, name, argv):
+ runcmd_run = globals()["runcmd_run"]
+ runcmd_exit_code_is = globals()["runcmd_exit_code_is"]
+
logging.debug(f"Starting daemon {name}")
logging.debug(f" ctx={ctx.as_dict()}")
logging.debug(f" name={name}")
@@ -22,7 +25,7 @@ def start_daemon(ctx, name, argv):
"stderr": f"{name}.stderr",
"stdout": f"{name}.stdout",
}
- runcmd(
+ runcmd_run(
ctx,
[
"/usr/sbin/daemonize",
@@ -37,7 +40,7 @@ def start_daemon(ctx, name, argv):
]
+ argv,
)
- exit_code_is(ctx, 0)
+ runcmd_exit_code_is(ctx, 0)
this["pid"] = int(open("ewww.pid").read().strip())
assert process_exists(this["pid"])
diff --git a/ewww.py b/subplot/ewww.py
index 9282d64..43e5946 100644
--- a/ewww.py
+++ b/subplot/ewww.py
@@ -1,15 +1,11 @@
#############################################################################
# Some helpers to make step functions simpler.
-import json
import logging
import os
import random
-import re
import shutil
-import signal
import socket
-import subprocess
import time
import urllib.parse
@@ -18,6 +14,7 @@ import yaml
# Name of Rust binary, debug-build.
def _binary(name):
+ srcdir = globals()["srcdir"]
return os.path.abspath(os.path.join(srcdir, "target", "debug", name))
@@ -56,6 +53,7 @@ def create_file(ctx, filename=None, content=None):
# Copy test certificate from source tree, where it's been created previously by
# ./check.
def copy_test_certificate(ctx, cert=None, key=None):
+ srcdir = globals()["srcdir"]
logging.debug(f"Copying test.pem, test.key from srcdir to {cert} and {key}")
shutil.copy(os.path.join(srcdir, "test.pem"), cert)
shutil.copy(os.path.join(srcdir, "test.key"), key)
@@ -63,6 +61,8 @@ def copy_test_certificate(ctx, cert=None, key=None):
# Start server using named configuration file.
def start_server(ctx, filename=None):
+ get_file = globals()["get_file"]
+ start_daemon = globals()["start_daemon"]
logging.debug(f"Starting ewww with config file {filename}")
config = get_file(filename).decode("UTF-8")
config = yaml.safe_load(config)
@@ -95,12 +95,14 @@ def port_open(host, port, timeout):
# Stop previously started server.
def stop_server(ctx):
+ stop_daemon = globals()["stop_daemon"]
logging.debug("Stopping ewww")
stop_daemon(ctx, "ewww")
# Make an HTTP request.
def request(ctx, method=None, url=None):
+ http_request = globals()["http_request"]
logging.debug(f"Making HTTP request to ewww: {method} {url}")
url, host = _url(ctx, url)
http_request(ctx, host=host, method=method, url=url)
diff --git a/ewww.yaml b/subplot/ewww.yaml
index 375558d..375558d 100644
--- a/ewww.yaml
+++ b/subplot/ewww.yaml
diff --git a/http.py b/subplot/http.py
index 5cff887..cfe8968 100644
--- a/http.py
+++ b/subplot/http.py
@@ -1,47 +1,45 @@
#############################################################################
# Some helpers to make HTTP requests and examine responses
-import json
import logging
-import os
-import random
-import re
-import shutil
-import signal
-import subprocess
-import time
-import urllib.parse
-
-import yaml
# Make an HTTP request.
def http_request(ctx, host=None, method=None, url=None):
+ runcmd_run = globals()["runcmd_run"]
+ runcmd_exit_code_is = globals()["runcmd_exit_code_is"]
logging.debug(f"Make HTTP request: {method} {url}")
- runcmd(ctx, ["curl", "-ksv", "-X", method, f"-HHost: {host}", url])
- exit_code_is(ctx, 0)
+ runcmd_run(ctx, ["curl", "-ksv", "-X", method, f"-HHost: {host}", url])
+ runcmd_exit_code_is(ctx, 0)
# Check status code of latest HTTP request.
def http_status_code_is(ctx, code=None):
+ assert_eq = globals()["assert_eq"]
+ runcmd_get_stderr = globals()["runcmd_get_stderr"]
+ stderr = runcmd_get_stderr(ctx)
logging.debug(f"Verifying status code of previous HTTP request is {code}")
- logging.debug(f" stderr={ctx['stderr']}")
+ logging.debug(f" stderr={stderr}")
pattern = f"\n< HTTP/2 {code} "
- assert_eq(pattern in ctx["stderr"], True)
+ assert_eq(pattern in stderr, True)
# Check a HTTP response header for latest request has a given value.
def http_header_is(ctx, header=None, value=None):
+ assert_eq = globals()["assert_eq"]
+ runcmd_get_stderr = globals()["runcmd_get_stderr"]
+ stderr = runcmd_get_stderr(ctx)
logging.debug(f"Verifying response has header {header}: {value}")
- s = ctx["stderr"]
pattern = f"\n< {header}: {value}"
- assert_eq(pattern in s, True)
+ assert_eq(pattern in stderr, True)
# Check a HTTP body response for latest request has a given value.
def http_body_is(ctx, body=None):
+ assert_eq = globals()["assert_eq"]
+ runcmd_get_stdout = globals()["runcmd_get_stdout"]
+ stdout = runcmd_get_stdout(ctx)
logging.debug(f"Verifying response body is {body!r}")
- logging.debug(f" actual body={ctx['stdout']!r}")
- s = ctx["stdout"]
+ logging.debug(f" actual body={stdout!r}")
body = body.encode("UTF8").decode("unicode-escape")
- assert_eq(body, s)
+ assert_eq(body, stdout)
diff --git a/subplot/runcmd.py b/subplot/runcmd.py
new file mode 100644
index 0000000..532b60b
--- /dev/null
+++ b/subplot/runcmd.py
@@ -0,0 +1,243 @@
+import logging
+import os
+import re
+import shlex
+import subprocess
+
+
+#
+# Helper functions.
+#
+
+# Get exit code or other stored data about the latest command run by
+# runcmd_run.
+
+
+def _runcmd_get(ctx, name):
+ ns = ctx.declare("_runcmd")
+ return ns[name]
+
+
+def runcmd_get_exit_code(ctx):
+ return _runcmd_get(ctx, "exit")
+
+
+def runcmd_get_stdout(ctx):
+ return _runcmd_get(ctx, "stdout")
+
+
+def runcmd_get_stdout_raw(ctx):
+ return _runcmd_get(ctx, "stdout.raw")
+
+
+def runcmd_get_stderr(ctx):
+ return _runcmd_get(ctx, "stderr")
+
+
+def runcmd_get_stderr_raw(ctx):
+ return _runcmd_get(ctx, "stderr.raw")
+
+
+def runcmd_get_argv(ctx):
+ return _runcmd_get(ctx, "argv")
+
+
+# Run a command, given an argv and other arguments for subprocess.Popen.
+#
+# This is meant to be a helper function, not bound directly to a step. The
+# stdout, stderr, and exit code are stored in the "_runcmd" namespace in the
+# ctx context.
+def runcmd_run(ctx, argv, **kwargs):
+ ns = ctx.declare("_runcmd")
+ env = dict(os.environ)
+ pp = ns.get("path-prefix")
+ if pp:
+ env["PATH"] = pp + ":" + env["PATH"]
+
+ logging.debug(f"runcmd_run")
+ logging.debug(f" argv: {argv}")
+ logging.debug(f" env: {env}")
+ p = subprocess.Popen(
+ argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, **kwargs
+ )
+ stdout, stderr = p.communicate("")
+ ns["argv"] = argv
+ ns["stdout.raw"] = stdout
+ ns["stderr.raw"] = stderr
+ ns["stdout"] = stdout.decode("utf-8")
+ ns["stderr"] = stderr.decode("utf-8")
+ ns["exit"] = p.returncode
+ logging.debug(f" ctx: {ctx}")
+ logging.debug(f" ns: {ns}")
+
+
+# Step: prepend srcdir to PATH whenever runcmd runs a command.
+def runcmd_helper_srcdir_path(ctx):
+ srcdir = globals()["srcdir"]
+ runcmd_prepend_to_path(ctx, srcdir)
+
+
+# Step: This creates a helper script.
+def runcmd_helper_script(ctx, filename=None):
+ get_file = globals()["get_file"]
+ with open(filename, "wb") as f:
+ f.write(get_file(filename))
+
+
+#
+# Step functions for running commands.
+#
+
+
+def runcmd_prepend_to_path(ctx, dirname=None):
+ ns = ctx.declare("_runcmd")
+ pp = ns.get("path-prefix", "")
+ if pp:
+ pp = f"{pp}:{dirname}"
+ else:
+ pp = dirname
+ ns["path-prefix"] = pp
+
+
+def runcmd_step(ctx, argv0=None, args=None):
+ runcmd_try_to_run(ctx, argv0=argv0, args=args)
+ runcmd_exit_code_is_zero(ctx)
+
+
+def runcmd_try_to_run(ctx, argv0=None, args=None):
+ argv = [shlex.quote(argv0)] + shlex.split(args)
+ runcmd_run(ctx, argv)
+
+
+#
+# Step functions for examining exit codes.
+#
+
+
+def runcmd_exit_code_is_zero(ctx):
+ runcmd_exit_code_is(ctx, exit=0)
+
+
+def runcmd_exit_code_is(ctx, exit=None):
+ assert_eq = globals()["assert_eq"]
+ assert_eq(runcmd_get_exit_code(ctx), int(exit))
+
+
+def runcmd_exit_code_is_nonzero(ctx):
+ runcmd_exit_code_is_not(ctx, exit=0)
+
+
+def runcmd_exit_code_is_not(ctx, exit=None):
+ assert_ne = globals()["assert_ne"]
+ assert_ne(runcmd_get_exit_code(ctx), int(exit))
+
+
+#
+# Step functions and helpers for examining output in various ways.
+#
+
+
+def runcmd_stdout_is(ctx, text=None):
+ _runcmd_output_is(runcmd_get_stdout(ctx), text)
+
+
+def runcmd_stdout_isnt(ctx, text=None):
+ _runcmd_output_isnt(runcmd_get_stdout(ctx), text)
+
+
+def runcmd_stderr_is(ctx, text=None):
+ _runcmd_output_is(runcmd_get_stderr(ctx), text)
+
+
+def runcmd_stderr_isnt(ctx, text=None):
+ _runcmd_output_isnt(runcmd_get_stderr(ctx), text)
+
+
+def _runcmd_output_is(actual, wanted):
+ assert_eq = globals()["assert_eq"]
+ wanted = bytes(wanted, "utf8").decode("unicode_escape")
+ logging.debug("_runcmd_output_is:")
+ logging.debug(f" actual: {actual!r}")
+ logging.debug(f" wanted: {wanted!r}")
+ assert_eq(actual, wanted)
+
+
+def _runcmd_output_isnt(actual, wanted):
+ assert_ne = globals()["assert_ne"]
+ wanted = bytes(wanted, "utf8").decode("unicode_escape")
+ logging.debug("_runcmd_output_isnt:")
+ logging.debug(f" actual: {actual!r}")
+ logging.debug(f" wanted: {wanted!r}")
+ assert_ne(actual, wanted)
+
+
+def runcmd_stdout_contains(ctx, text=None):
+ _runcmd_output_contains(runcmd_get_stdout(ctx), text)
+
+
+def runcmd_stdout_doesnt_contain(ctx, text=None):
+ _runcmd_output_doesnt_contain(runcmd_get_stdout(ctx), text)
+
+
+def runcmd_stderr_contains(ctx, text=None):
+ _runcmd_output_contains(runcmd_get_stderr(ctx), text)
+
+
+def runcmd_stderr_doesnt_contain(ctx, text=None):
+ _runcmd_output_doesnt_contain(runcmd_get_stderr(ctx), text)
+
+
+def _runcmd_output_contains(actual, wanted):
+ assert_eq = globals()["assert_eq"]
+ wanted = bytes(wanted, "utf8").decode("unicode_escape")
+ logging.debug("_runcmd_output_contains:")
+ logging.debug(f" actual: {actual!r}")
+ logging.debug(f" wanted: {wanted!r}")
+ assert_eq(wanted in actual, True)
+
+
+def _runcmd_output_doesnt_contain(actual, wanted):
+ assert_ne = globals()["assert_ne"]
+ wanted = bytes(wanted, "utf8").decode("unicode_escape")
+ logging.debug("_runcmd_output_doesnt_contain:")
+ logging.debug(f" actual: {actual!r}")
+ logging.debug(f" wanted: {wanted!r}")
+ assert_ne(wanted in actual, True)
+
+
+def runcmd_stdout_matches_regex(ctx, regex=None):
+ _runcmd_output_matches_regex(runcmd_get_stdout(ctx), regex)
+
+
+def runcmd_stdout_doesnt_match_regex(ctx, regex=None):
+ _runcmd_output_doesnt_match_regex(runcmd_get_stdout(ctx), regex)
+
+
+def runcmd_stderr_matches_regex(ctx, regex=None):
+ _runcmd_output_matches_regex(runcmd_get_stderr(ctx), regex)
+
+
+def runcmd_stderr_doesnt_match_regex(ctx, regex=None):
+ _runcmd_output_doesnt_match_regex(runcmd_get_stderr(ctx), regex)
+
+
+def _runcmd_output_matches_regex(actual, regex):
+ assert_ne = globals()["assert_ne"]
+ r = re.compile(regex)
+ m = r.search(actual)
+ logging.debug("_runcmd_output_matches_regex:")
+ logging.debug(f" actual: {actual!r}")
+ logging.debug(f" regex: {regex!r}")
+ logging.debug(f" match: {m}")
+ assert_ne(m, None)
+
+
+def _runcmd_output_doesnt_match_regex(actual, regex):
+ assert_eq = globals()["assert_eq"]
+ r = re.compile(regex)
+ m = r.search(actual)
+ logging.debug("_runcmd_output_doesnt_match_regex:")
+ logging.debug(f" actual: {actual!r}")
+ logging.debug(f" regex: {regex!r}")
+ logging.debug(f" match: {m}")
+ assert_eq(m, None)
diff --git a/subplot/runcmd.yaml b/subplot/runcmd.yaml
new file mode 100644
index 0000000..48dde90
--- /dev/null
+++ b/subplot/runcmd.yaml
@@ -0,0 +1,83 @@
+# Steps to run commands.
+
+- given: helper script {filename} for runcmd
+ function: runcmd_helper_script
+
+- given: srcdir is in the PATH
+ function: runcmd_helper_srcdir_path
+
+- when: I run (?P<argv0>\S+)(?P<args>.*)
+ regex: true
+ function: runcmd_step
+
+- when: I try to run (?P<argv0>\S+)(?P<args>.*)
+ regex: true
+ function: runcmd_try_to_run
+
+# Steps to examine exit code of latest command.
+
+- then: exit code is {exit}
+ function: runcmd_exit_code_is
+
+- then: exit code is not {exit}
+ function: runcmd_exit_code_is_not
+
+- then: command is successful
+ function: runcmd_exit_code_is_zero
+
+- then: command fails
+ function: runcmd_exit_code_is_nonzero
+
+# Steps to examine stdout/stderr for exact content.
+
+- then: stdout is exactly "(?P<text>.*)"
+ regex: true
+ function: runcmd_stdout_is
+
+- then: "stdout isn't exactly \"(?P<text>.*)\""
+ regex: true
+ function: runcmd_stdout_isnt
+
+- then: stderr is exactly "(?P<text>.*)"
+ regex: true
+ function: runcmd_stderr_is
+
+- then: "stderr isn't exactly \"(?P<text>.*)\""
+ regex: true
+ function: runcmd_stderr_isnt
+
+# Steps to examine stdout/stderr for sub-strings.
+
+- then: stdout contains "(?P<text>.*)"
+ regex: true
+ function: runcmd_stdout_contains
+
+- then: "stdout doesn't contain \"(?P<text>.*)\""
+ regex: true
+ function: runcmd_stdout_doesnt_contain
+
+- then: stderr contains "(?P<text>.*)"
+ regex: true
+ function: runcmd_stderr_contains
+
+- then: "stderr doesn't contain \"(?P<text>.*)\""
+ regex: true
+ function: runcmd_stderr_doesnt_contain
+
+# Steps to match stdout/stderr against regular expressions.
+
+- then: stdout matches regex (?P<regex>.*)
+ regex: true
+ function: runcmd_stdout_matches_regex
+
+- then: stdout doesn't match regex (?P<regex>.*)
+ regex: true
+ function: runcmd_stdout_doesnt_match_regex
+
+- then: stderr matches regex (?P<regex>.*)
+ regex: true
+ function: runcmd_stderr_matches_regex
+
+- then: stderr doesn't match regex (?P<regex>.*)
+ regex: true
+ function: runcmd_stderr_doesnt_match_regex