From e34beeecfc807bd3afb9d5b6c2c764fd71027cde Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Wed, 14 Oct 2020 09:19:06 +0300 Subject: refactor: move subplot files to subplot/ The root of the source tree was getting a little crowded. --- daemon.py | 74 ------------------------------------ ewww.md | 12 +++--- ewww.py | 106 ---------------------------------------------------- ewww.yaml | 37 ------------------ http.py | 47 ----------------------- runcmd.py | 73 ------------------------------------ runcmd.yaml | 13 ------- subplot/daemon.py | 74 ++++++++++++++++++++++++++++++++++++ subplot/ewww.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++ subplot/ewww.yaml | 37 ++++++++++++++++++ subplot/http.py | 47 +++++++++++++++++++++++ subplot/runcmd.py | 73 ++++++++++++++++++++++++++++++++++++ subplot/runcmd.yaml | 13 +++++++ 13 files changed, 356 insertions(+), 356 deletions(-) delete mode 100644 daemon.py delete mode 100644 ewww.py delete mode 100644 ewww.yaml delete mode 100644 http.py delete mode 100644 runcmd.py delete mode 100644 runcmd.yaml create mode 100644 subplot/daemon.py create mode 100644 subplot/ewww.py create mode 100644 subplot/ewww.yaml create mode 100644 subplot/http.py create mode 100644 subplot/runcmd.py create mode 100644 subplot/runcmd.yaml diff --git a/daemon.py b/daemon.py deleted file mode 100644 index 585fe5a..0000000 --- a/daemon.py +++ /dev/null @@ -1,74 +0,0 @@ -############################################################################# -# 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, signalno): - logging.debug(f"Terminating process {pid} with signal {signalno}") - try: - os.kill(pid, signalno) - except ProcessLookupError: - logging.debug("Process did not actually exist (anymore?)") - pass 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/ewww.py b/ewww.py deleted file mode 100644 index 9282d64..0000000 --- a/ewww.py +++ /dev/null @@ -1,106 +0,0 @@ -############################################################################# -# 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 - -import yaml - - -# Name of Rust binary, debug-build. -def _binary(name): - return os.path.abspath(os.path.join(srcdir, "target", "debug", name)) - - -# Write a file with given content. -def _write(filename, content): - open(filename, "w").write(content) - - -# Construct a URL that points to server running on localhost by -# replacing the actual scheme and host with ones that work for test. -def _url(ctx, url): - port = ctx["config"]["port"] - c = urllib.parse.urlparse(url) - host = c[1] - c = (c[0], "localhost:{}".format(port)) + c[2:] - return urllib.parse.urlunparse(c), host - - -############################################################################# -# The actual step functions. - - -# Fail: use this for unimplemented steps. -def fixme(*args, **kwargs): - assert 0 - - -# 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) - - -# 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) - port = 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) - - start_daemon(ctx, "ewww", [_binary("ewww"), filename]) - - if not port_open("localhost", port, 5.0): - stderr = open(ctx["daemon"]["ewww"]["stderr"]).read() - logging.debug(f"Stderr from daemon: {stderr!r}") - - -# Wait for a port to be open -def port_open(host, port, timeout): - logging.debug(f"Waiting for port localhost:{port} to be available") - started = time.time() - while time.time() < started + timeout: - try: - socket.create_connection((host, port), timeout=timeout) - return True - except socket.error: - pass - logging.error(f"Port localhost:{port} is not open") - return False - - -# Stop previously started server. -def stop_server(ctx): - logging.debug("Stopping ewww") - stop_daemon(ctx, "ewww") - - -# 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) - http_request(ctx, host=host, method=method, url=url) diff --git a/ewww.yaml b/ewww.yaml deleted file mode 100644 index 375558d..0000000 --- a/ewww.yaml +++ /dev/null @@ -1,37 +0,0 @@ -- given: a self-signed certificate as {cert}, using key {key} - function: copy_test_certificate - -- given: a running server using config file {filename} - function: start_server - cleanup: stop_server - -- given: "{count} files in {dirname}" - function: fixme - -- given: file (?P\S+) with "(?P.*)" - regex: true - function: create_file - -- then: I am redirected to {location} - function: fixme - -- then: I can do at least {number} requests per second - function: fixme - -- then: I get status code {code} - function: http_status_code_is - -- then: 'header (?P
\S+) is "(?P.+)"' - regex: true - function: http_header_is - -- then: 'body is "(?P.*)"' - regex: true - function: http_body_is - -- when: I request {method} {url} - function: request - -- when: I request files under {url} in random order {count} times - function: fixme - diff --git a/http.py b/http.py deleted file mode 100644 index 5cff887..0000000 --- a/http.py +++ /dev/null @@ -1,47 +0,0 @@ -############################################################################# -# 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}") - logging.debug(f" stderr={ctx['stderr']}") - 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}") - logging.debug(f" actual body={ctx['stdout']!r}") - s = ctx["stdout"] - body = body.encode("UTF8").decode("unicode-escape") - assert_eq(body, s) 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.+)/ - function: stdout_contains - regex: true - -- then: stderr matches /(?P.+)/ - function: stderr_contains - regex: true - -- then: program finished successfully - function: exit_code_zero diff --git a/subplot/daemon.py b/subplot/daemon.py new file mode 100644 index 0000000..585fe5a --- /dev/null +++ b/subplot/daemon.py @@ -0,0 +1,74 @@ +############################################################################# +# 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, signalno): + logging.debug(f"Terminating process {pid} with signal {signalno}") + try: + os.kill(pid, signalno) + except ProcessLookupError: + logging.debug("Process did not actually exist (anymore?)") + pass diff --git a/subplot/ewww.py b/subplot/ewww.py new file mode 100644 index 0000000..9282d64 --- /dev/null +++ b/subplot/ewww.py @@ -0,0 +1,106 @@ +############################################################################# +# 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 + +import yaml + + +# Name of Rust binary, debug-build. +def _binary(name): + return os.path.abspath(os.path.join(srcdir, "target", "debug", name)) + + +# Write a file with given content. +def _write(filename, content): + open(filename, "w").write(content) + + +# Construct a URL that points to server running on localhost by +# replacing the actual scheme and host with ones that work for test. +def _url(ctx, url): + port = ctx["config"]["port"] + c = urllib.parse.urlparse(url) + host = c[1] + c = (c[0], "localhost:{}".format(port)) + c[2:] + return urllib.parse.urlunparse(c), host + + +############################################################################# +# The actual step functions. + + +# Fail: use this for unimplemented steps. +def fixme(*args, **kwargs): + assert 0 + + +# 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) + + +# 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) + port = 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) + + start_daemon(ctx, "ewww", [_binary("ewww"), filename]) + + if not port_open("localhost", port, 5.0): + stderr = open(ctx["daemon"]["ewww"]["stderr"]).read() + logging.debug(f"Stderr from daemon: {stderr!r}") + + +# Wait for a port to be open +def port_open(host, port, timeout): + logging.debug(f"Waiting for port localhost:{port} to be available") + started = time.time() + while time.time() < started + timeout: + try: + socket.create_connection((host, port), timeout=timeout) + return True + except socket.error: + pass + logging.error(f"Port localhost:{port} is not open") + return False + + +# Stop previously started server. +def stop_server(ctx): + logging.debug("Stopping ewww") + stop_daemon(ctx, "ewww") + + +# 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) + http_request(ctx, host=host, method=method, url=url) diff --git a/subplot/ewww.yaml b/subplot/ewww.yaml new file mode 100644 index 0000000..375558d --- /dev/null +++ b/subplot/ewww.yaml @@ -0,0 +1,37 @@ +- given: a self-signed certificate as {cert}, using key {key} + function: copy_test_certificate + +- given: a running server using config file {filename} + function: start_server + cleanup: stop_server + +- given: "{count} files in {dirname}" + function: fixme + +- given: file (?P\S+) with "(?P.*)" + regex: true + function: create_file + +- then: I am redirected to {location} + function: fixme + +- then: I can do at least {number} requests per second + function: fixme + +- then: I get status code {code} + function: http_status_code_is + +- then: 'header (?P
\S+) is "(?P.+)"' + regex: true + function: http_header_is + +- then: 'body is "(?P.*)"' + regex: true + function: http_body_is + +- when: I request {method} {url} + function: request + +- when: I request files under {url} in random order {count} times + function: fixme + diff --git a/subplot/http.py b/subplot/http.py new file mode 100644 index 0000000..5cff887 --- /dev/null +++ b/subplot/http.py @@ -0,0 +1,47 @@ +############################################################################# +# 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}") + logging.debug(f" stderr={ctx['stderr']}") + 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}") + logging.debug(f" actual body={ctx['stdout']!r}") + s = ctx["stdout"] + body = body.encode("UTF8").decode("unicode-escape") + assert_eq(body, s) diff --git a/subplot/runcmd.py b/subplot/runcmd.py new file mode 100644 index 0000000..631d8c0 --- /dev/null +++ b/subplot/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/subplot/runcmd.yaml b/subplot/runcmd.yaml new file mode 100644 index 0000000..02e5ee1 --- /dev/null +++ b/subplot/runcmd.yaml @@ -0,0 +1,13 @@ +- then: exit code is non-zero + function: exit_code_nonzero + +- then: output matches /(?P.+)/ + function: stdout_contains + regex: true + +- then: stderr matches /(?P.+)/ + function: stderr_contains + regex: true + +- then: program finished successfully + function: exit_code_zero -- cgit v1.2.1