summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-07-19 07:51:55 +0000
committerLars Wirzenius <liw@liw.fi>2020-07-19 07:51:55 +0000
commitd5b2b69c6e34822f417a5e4c3b3a533b06a52bf1 (patch)
tree5bccd3420b2bdf59c9c4df1424d9868cec25df0e
parentf70a21ae966d0bcc791cf12d02fe88910213ac08 (diff)
parent50a778a59366b19ba1e94ca9d7f0ac9ff55f54a2 (diff)
downloadewww-d5b2b69c6e34822f417a5e4c3b3a533b06a52bf1.tar.gz
Merge branch 'subplot-lib' into 'master'
Use Subplot runcmd library, refactor functions into reuseable modules See merge request larswirzenius/ewww!5
-rw-r--r--daemon.py69
-rw-r--r--ewww.md10
-rw-r--r--ewww.py61
-rw-r--r--ewww.yaml2
-rw-r--r--http.py45
-rw-r--r--runcmd.py73
-rw-r--r--runcmd.yaml13
7 files changed, 220 insertions, 53 deletions
diff --git a/daemon.py b/daemon.py
new file mode 100644
index 0000000..33057bd
--- /dev/null
+++ b/daemon.py
@@ -0,0 +1,69 @@
+#############################################################################
+# Start and stop daemons, or background processes.
+
+
+import logging
+import os
+import signal
+
+
+# Start a process in the background.
+def start_daemon(ctx, name, argv):
+ logging.debug(f"Starting daemon {name}")
+ logging.debug(f" ctx={ctx.as_dict()}")
+ logging.debug(f" name={name}")
+ logging.debug(f" argv={argv}")
+
+ if "daemon" not in ctx.as_dict():
+ ctx["daemon"] = {}
+ assert name not in ctx["daemon"]
+ this = ctx["daemon"][name] = {
+ "pid-file": f"{name}.pid",
+ "stderr": f"{name}.stderr",
+ "stdout": f"{name}.stdout",
+ }
+ runcmd(
+ ctx,
+ [
+ "/usr/sbin/daemonize",
+ "-c",
+ os.getcwd(),
+ "-p",
+ this["pid-file"],
+ "-e",
+ this["stderr"],
+ "-o",
+ this["stdout"],
+ ]
+ + argv,
+ )
+ exit_code_is(ctx, 0)
+ this["pid"] = int(open("ewww.pid").read().strip())
+ assert process_exists(this["pid"])
+
+ logging.debug(f"Started daemon {name}")
+ logging.debug(f" ctx={ctx.as_dict()}")
+
+
+# Stop a daemon.
+def stop_daemon(ctx, name):
+ logging.debug(f"Stopping daemon {name}")
+ logging.debug(f" ctx={ctx.as_dict()}")
+ logging.debug(f" ctx['daemon']={ctx.as_dict()['daemon']}")
+
+ this = ctx["daemon"][name]
+ terminate_process(this["pid"], signal.SIGKILL)
+
+
+# Does a process exist?
+def process_exists(pid):
+ try:
+ os.kill(pid, 0)
+ except ProcessLookupError:
+ return False
+ return True
+
+
+# Terminate process.
+def terminate_process(pid, signal):
+ os.kill(pid, signal)
diff --git a/ewww.md b/ewww.md
index 8e60f8b..ed2f663 100644
--- a/ewww.md
+++ b/ewww.md
@@ -182,8 +182,14 @@ and allow is "GET HEAD"
---
title: "Ewww &mdash; a Web server for static sites"
author: Lars Wirzenius
-bindings: ewww.yaml
-functions: ewww.py
+bindings:
+ - ewww.yaml
+ - runcmd.yaml
+functions:
+ - ewww.py
+ - daemon.py
+ - http.py
+ - runcmd.py
classes:
- scenario-disabled
...
diff --git a/ewww.py b/ewww.py
index 4f06b47..ded5064 100644
--- a/ewww.py
+++ b/ewww.py
@@ -2,6 +2,7 @@
# Some helpers to make step functions simpler.
import json
+import logging
import os
import random
import re
@@ -62,6 +63,7 @@ def fixme(*args, **kwargs):
# Create a file.
def create_file(ctx, filename=None, content=None):
+ logging.debug(f"Creating file {filename} with {content}")
dirname = os.path.dirname(filename)
os.makedirs(dirname)
_write(filename, content)
@@ -70,74 +72,33 @@ 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):
+ 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)
# Start server using named configuration file.
def start_server(ctx, filename=None):
+ logging.debug(f"Starting ewww with config file {filename}")
config = get_file(filename).decode("UTF-8")
config = yaml.safe_load(config)
config["port"] = random.randint(2000, 30000)
+ logging.debug(f"Picked randomly port for ewww: {config['port']}")
ctx["config"] = config
config = yaml.safe_dump(config)
_write(filename, config)
- _run(
- ctx,
- [
- "/usr/sbin/daemonize",
- "-c",
- os.getcwd(),
- "-p",
- "ewww.pid",
- "-o",
- "ewww.stdout",
- "-e",
- "ewww.stderr",
- _binary("ewww"),
- filename,
- ],
- )
- _run_exit(ctx, 0)
-
- ctx["pid"] = int(open("ewww.pid").read().strip())
+ start_daemon(ctx, "ewww", [_binary("ewww"), filename])
# Stop previously started server.
def stop_server(ctx):
- os.kill(ctx["pid"], signal.SIGKILL)
+ logging.debug("Stopping ewww")
+ stop_daemon(ctx, "ewww")
-# Make a HTTP request.
+# Make an HTTP request.
def request(ctx, method=None, url=None):
+ logging.debug(f"Making HTTP request to ewww: {method} {url}")
url, host = _url(ctx, url)
- print(url)
- _run(ctx, ["curl", "-ksv", "-X", method, "-HHost: {}".format(host), url])
- _run_exit(ctx, 0)
-
-
-# Check status code of latest HTTP request.
-def status_code_is(ctx, code=None):
- pattern = "\n< HTTP/2 {} ".format(code)
- assert_eq(pattern in ctx["stderr"], True)
-
-
-# Check a HTTP response header for latest request has a given value.
-def http_header_is(ctx, header=None, value=None):
- s = ctx["stderr"]
- pattern = "\n< {}: {}".format(header, value)
- if pattern not in s:
- print("stderr:", repr(s))
- print("pattern:", repr(pattern))
- assert_eq(pattern in s, True)
-
-
-# Check a HTTP body response for latest request has a given value.
-def http_body_is(ctx, body=None):
- s = ctx["stdout"]
- body = body.encode("UTF8").decode("unicode-escape")
- if body != s:
- print("stdout:", repr(s))
- prin("pattern:", repr(body))
- assert_eq(body, s)
+ http_request(ctx, host=host, method=method, url=url)
diff --git a/ewww.yaml b/ewww.yaml
index d1a13ab..08a13e4 100644
--- a/ewww.yaml
+++ b/ewww.yaml
@@ -15,7 +15,7 @@
function: fixme
- then: I get status code {code}
- function: status_code_is
+ function: http_status_code_is
- then: 'header (?P<header>\S+) is "(?P<value>.+)"'
regex: true
diff --git a/http.py b/http.py
new file mode 100644
index 0000000..fd552a6
--- /dev/null
+++ b/http.py
@@ -0,0 +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):
+ logging.debug(f"Make HTTP request: {method} {url}")
+ runcmd(ctx, ["curl", "-ksv", "-X", method, f"-HHost: {host}", url])
+ exit_code_is(ctx, 0)
+
+
+# Check status code of latest HTTP request.
+def http_status_code_is(ctx, code=None):
+ logging.debug(f"Verifying status code of previous HTTP request is {code}")
+ pattern = f"\n< HTTP/2 {code} "
+ assert_eq(pattern in ctx["stderr"], True)
+
+
+# Check a HTTP response header for latest request has a given value.
+def http_header_is(ctx, header=None, value=None):
+ logging.debug(f"Verifying response has header {header}: {value}")
+ s = ctx["stderr"]
+ pattern = f"\n< {header}: {value}"
+ assert_eq(pattern in s, True)
+
+
+# Check a HTTP body response for latest request has a given value.
+def http_body_is(ctx, body=None):
+ logging.debug(f"Verifying response body is {body!r}")
+ s = ctx["stdout"]
+ body = body.encode("UTF8").decode("unicode-escape")
+ assert_eq(body, s)
diff --git a/runcmd.py b/runcmd.py
new file mode 100644
index 0000000..631d8c0
--- /dev/null
+++ b/runcmd.py
@@ -0,0 +1,73 @@
+# 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
new file mode 100644
index 0000000..02e5ee1
--- /dev/null
+++ b/runcmd.yaml
@@ -0,0 +1,13 @@
+- 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