diff options
author | Lars Wirzenius <liw@liw.fi> | 2020-07-19 07:51:55 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2020-07-19 07:51:55 +0000 |
commit | d5b2b69c6e34822f417a5e4c3b3a533b06a52bf1 (patch) | |
tree | 5bccd3420b2bdf59c9c4df1424d9868cec25df0e | |
parent | f70a21ae966d0bcc791cf12d02fe88910213ac08 (diff) | |
parent | 50a778a59366b19ba1e94ca9d7f0ac9ff55f54a2 (diff) | |
download | ewww-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.py | 69 | ||||
-rw-r--r-- | ewww.md | 10 | ||||
-rw-r--r-- | ewww.py | 61 | ||||
-rw-r--r-- | ewww.yaml | 2 | ||||
-rw-r--r-- | http.py | 45 | ||||
-rw-r--r-- | runcmd.py | 73 | ||||
-rw-r--r-- | runcmd.yaml | 13 |
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) @@ -182,8 +182,14 @@ and allow is "GET HEAD" --- title: "Ewww — 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 ... @@ -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) @@ -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 @@ -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 |