summaryrefslogtreecommitdiff
path: root/subplot
diff options
context:
space:
mode:
Diffstat (limited to 'subplot')
-rw-r--r--subplot/client.py76
-rw-r--r--subplot/client.yaml66
-rw-r--r--subplot/data.py170
-rw-r--r--subplot/data.yaml98
-rw-r--r--subplot/server.py69
-rw-r--r--subplot/server.yaml99
-rw-r--r--subplot/vendored/daemon.md38
-rw-r--r--subplot/vendored/daemon.py139
-rw-r--r--subplot/vendored/daemon.yaml17
-rw-r--r--subplot/vendored/files.md82
-rw-r--r--subplot/vendored/files.py158
-rw-r--r--subplot/vendored/files.yaml62
-rw-r--r--subplot/vendored/runcmd.md170
-rw-r--r--subplot/vendored/runcmd.py252
-rw-r--r--subplot/vendored/runcmd.yaml83
15 files changed, 483 insertions, 1096 deletions
diff --git a/subplot/client.py b/subplot/client.py
index d450b4c..09b1556 100644
--- a/subplot/client.py
+++ b/subplot/client.py
@@ -3,15 +3,35 @@ import os
import yaml
+def start_obnam(ctx):
+ start_chunk_server = globals()["start_chunk_server"]
+ install_obnam(ctx)
+ start_chunk_server(ctx)
+
+
+def stop_obnam(ctx):
+ stop_chunk_server = globals()["stop_chunk_server"]
+ stop_chunk_server(ctx)
+ uninstall_obnam(ctx)
+
+
def install_obnam(ctx):
runcmd_prepend_to_path = globals()["runcmd_prepend_to_path"]
srcdir = globals()["srcdir"]
# Add the directory with built Rust binaries to the path.
- runcmd_prepend_to_path(ctx, dirname=os.path.join(srcdir, "target", "debug"))
+ default_target = os.path.join(srcdir, "target")
+ target = os.environ.get("CARGO_TARGET_DIR", default_target)
+ runcmd_prepend_to_path(ctx, dirname=os.path.join(target, "debug"))
+ ctx["server-binary"] = os.path.join(target, "debug", "obnam-server")
-def configure_client(ctx, filename=None):
+def uninstall_obnam(ctx):
+ runcmd_run = globals()["runcmd_run"]
+ runcmd_run(ctx, ["chmod", "-R", "u+rwX", "."])
+
+
+def configure_client_without_init(ctx, filename=None):
get_file = globals()["get_file"]
assert ctx.get("server_url") is not None
@@ -20,42 +40,37 @@ def configure_client(ctx, filename=None):
config = yaml.safe_load(config)
config["server_url"] = ctx["server_url"]
+ logging.debug(f"client config {filename}: {config}")
+ dirname = os.path.expanduser("~/.config/obnam")
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ filename = os.path.join(dirname, "obnam.yaml")
+ logging.debug(f"configure_client: filename={filename}")
with open(filename, "w") as f:
yaml.safe_dump(config, stream=f)
-def run_obnam_restore(ctx, filename=None, genid=None, todir=None):
- genid = ctx["vars"][genid]
- run_obnam_restore_with_genref(ctx, filename=filename, genref=genid, todir=todir)
-
+def configure_client_with_init(ctx, filename=None):
+ runcmd_run = globals()["runcmd_run"]
+ runcmd_exit_code_is_zero = globals()["runcmd_exit_code_is_zero"]
-def run_obnam_restore_latest(ctx, filename=None, todir=None):
- run_obnam_restore_with_genref(ctx, filename=filename, genref="latest", todir=todir)
+ configure_client_without_init(ctx, filename=filename)
+ runcmd_run(ctx, ["obnam", "init", "--insecure-passphrase=hunter2"])
+ runcmd_exit_code_is_zero(ctx)
-def run_obnam_restore_with_genref(ctx, filename=None, genref=None, todir=None):
+def run_obnam_restore(ctx, genid=None, todir=None):
runcmd_run = globals()["runcmd_run"]
- runcmd_run(
- ctx,
- [
- "env",
- "RUST_LOG=obnam",
- "obnam",
- "--config",
- filename,
- "restore",
- genref,
- todir,
- ],
- )
-
-
-def run_obnam_get_chunk(ctx, filename=None, gen_id=None, todir=None):
+ genref = ctx["vars"][genid]
+ runcmd_run(ctx, ["env", "RUST_LOG=obnam", "obnam", "restore", genref, todir])
+
+
+def run_obnam_get_chunk(ctx, gen_id=None, todir=None):
runcmd_run = globals()["runcmd_run"]
gen_id = ctx["vars"][gen_id]
logging.debug(f"run_obnam_get_chunk: gen_id={gen_id}")
- runcmd_run(ctx, ["obnam", "--config", filename, "get-chunk", gen_id])
+ runcmd_run(ctx, ["obnam", "get-chunk", gen_id])
def capture_generation_id(ctx, varname=None):
@@ -111,3 +126,12 @@ def stdout_matches_file(ctx, filename=None):
stdout = runcmd_get_stdout(ctx)
data = open(filename).read()
assert_eq(stdout, data)
+
+
+def stdout_contains_home_dir_path(ctx, path=None):
+ runcmd_get_stdout = globals()["runcmd_get_stdout"]
+ stdout = runcmd_get_stdout(ctx)
+ wanted = os.path.abspath(os.path.normpath("./" + path))
+ logging.debug(f"stdout_contains_home_dir_path: stdout={stdout!r}")
+ logging.debug(f"stdout_contains_home_dir_path: wanted={wanted!r}")
+ assert wanted in stdout
diff --git a/subplot/client.yaml b/subplot/client.yaml
index b1f9b19..104f479 100644
--- a/subplot/client.yaml
+++ b/subplot/client.yaml
@@ -1,32 +1,70 @@
+- given: "a working Obnam system"
+ impl:
+ python:
+ function: start_obnam
+ cleanup: stop_obnam
+
- given: "an installed obnam"
- function: install_obnam
+ impl:
+ python:
+ function: install_obnam
+ cleanup: uninstall_obnam
- given: "a client config based on {filename}"
- function: configure_client
+ impl:
+ python:
+ function: configure_client_with_init
+ types:
+ filename: file
-- when: "I invoke obnam --config {filename} restore <{genid}> {todir}"
- function: run_obnam_restore
+- given: "a client config, without passphrase, based on {filename}"
+ impl:
+ python:
+ function: configure_client_without_init
+ types:
+ filename: file
-- when: "I invoke obnam --config {filename} restore latest {todir}"
- function: run_obnam_restore_latest
+- when: "I invoke obnam restore <{genid}> {todir}"
+ impl:
+ python:
+ function: run_obnam_restore
-- when: "I invoke obnam --config {filename} get-chunk <{gen_id}>"
- function: run_obnam_get_chunk
+- when: "I invoke obnam get-chunk <{gen_id}>"
+ impl:
+ python:
+ function: run_obnam_get_chunk
- then: "backup generation is {varname}"
- function: capture_generation_id
+ impl:
+ python:
+ function: capture_generation_id
- then: "generation list contains <{gen_id}>"
- function: generation_list_contains
+ impl:
+ python:
+ function: generation_list_contains
- then: "file {filename} was backed up because it was new"
- function: file_was_new
+ impl:
+ python:
+ function: file_was_new
- then: "file {filename} was backed up because it was changed"
- function: file_was_changed
+ impl:
+ python:
+ function: file_was_changed
- then: "file {filename} was not backed up because it was unchanged"
- function: file_was_unchanged
+ impl:
+ python:
+ function: file_was_unchanged
- then: "stdout matches file {filename}"
- function: stdout_matches_file
+ impl:
+ python:
+ function: stdout_matches_file
+
+- then: "stdout contains home directory followed by {path}"
+ impl:
+ python:
+ function: stdout_contains_home_dir_path
diff --git a/subplot/data.py b/subplot/data.py
index a24cd0c..583e52d 100644
--- a/subplot/data.py
+++ b/subplot/data.py
@@ -1,16 +1,41 @@
+import json
import logging
import os
import random
+import socket
+import stat
+import yaml
-def create_file_with_random_data(ctx, filename=None):
- N = 128
- data = "".join(chr(random.randint(0, 255)) for i in range(N)).encode("UTF-8")
+def create_file_with_given_data(ctx, filename=None, data=None):
+ logging.debug(f"creating file {filename} with {data!r}")
dirname = os.path.dirname(filename) or "."
- logging.debug(f"create_file_with_random_data: dirname={dirname}")
os.makedirs(dirname, exist_ok=True)
with open(filename, "wb") as f:
- f.write(data)
+ f.write(data.encode("UTF-8"))
+
+
+def create_file_with_random_data(ctx, filename=None):
+ N = 128
+ data = "".join(chr(random.randint(0, 255)) for i in range(N))
+ create_file_with_given_data(ctx, filename=filename, data=data)
+
+
+def create_unix_socket(ctx, filename=None):
+ fd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ fd.bind(filename)
+
+
+def create_fifo(ctx, filename=None):
+ os.mkfifo(filename)
+
+
+def create_cachedir_tag_in(ctx, dirpath=None):
+ filepath = f"{dirpath}/CACHEDIR.TAG"
+ logging.debug(f"creating {filepath}")
+ os.makedirs(dirpath, exist_ok=True)
+ with open(filepath, "w") as f:
+ f.write("Signature: 8a477f597d28d172789f06886806bc55")
def create_nonutf8_filename(ctx, dirname=None):
@@ -24,7 +49,7 @@ def chmod_file(ctx, filename=None, mode=None):
def create_symlink(ctx, linkname=None, target=None):
- os.symlink(linkname, target)
+ os.symlink(target, linkname)
def create_manifest_of_live(ctx, dirname=None, manifest=None):
@@ -46,7 +71,26 @@ def _create_manifest_of_directory(ctx, dirname=None, manifest=None):
runcmd_run(ctx, ["find", "-exec", "summain", "{}", "+"], cwd=dirname)
assert runcmd_get_exit_code(ctx) == 0
stdout = runcmd_get_stdout(ctx)
- open(manifest, "w").write(stdout)
+ with open(manifest, "w") as f:
+ f.write(stdout)
+
+
+def file_is_restored(ctx, filename=None, restored=None):
+ filename = os.path.join(restored, "./" + filename)
+ exists = os.path.exists(filename)
+ logging.debug(f"restored? {filename} {exists}")
+ assert exists
+
+
+def file_is_not_restored(ctx, filename=None, restored=None):
+ filename = os.path.join(restored, "./" + filename)
+ logging.info(r"verifying that {filename} does not exist")
+ try:
+ exists = os.path.exists(filename)
+ except PermissionError:
+ exists = False
+ logging.debug(f"restored? {filename} {exists}")
+ assert not exists
def files_match(ctx, first=None, second=None):
@@ -57,3 +101,115 @@ def files_match(ctx, first=None, second=None):
logging.debug(f"files_match: f:\n{f}")
logging.debug(f"files_match: s:\n{s}")
assert_eq(f, s)
+
+
+def convert_yaml_to_json(ctx, yaml_name=None, json_name=None):
+ with open(yaml_name) as f:
+ obj = yaml.safe_load(f)
+ with open(json_name, "w") as f:
+ json.dump(obj, f)
+
+
+def match_stdout_to_json_file_superset(ctx, filename=None):
+ runcmd_get_stdout = globals()["runcmd_get_stdout"]
+ assert_eq = globals()["assert_eq"]
+ assert_dict_eq = globals()["assert_dict_eq"]
+
+ stdout = runcmd_get_stdout(ctx)
+ stdout = json.loads(stdout.strip())
+ obj = json.load(open(filename))
+ logging.debug(f"match_stdout_to_json_file: stdout={stdout!r}")
+ logging.debug(f"match_stdout_to_json_file: file={obj!r}")
+
+ if isinstance(obj, dict):
+ stdout = {key: value for key, value in stdout.items() if key in obj}
+ assert_dict_eq(obj, stdout)
+ elif isinstance(obj, list):
+ obj = {"key": obj}
+ stdout = {"key": stdout}
+ assert_dict_eq(obj, stdout)
+ assert_dict_eq(obj, stdout)
+ else:
+ assert_eq(obj, stdout)
+
+
+def match_stdout_to_json_file_exactly(ctx, filename=None):
+ runcmd_get_stdout = globals()["runcmd_get_stdout"]
+ assert_eq = globals()["assert_eq"]
+ assert_dict_eq = globals()["assert_dict_eq"]
+
+ stdout = runcmd_get_stdout(ctx)
+ stdout = json.loads(stdout.strip())
+ obj = json.load(open(filename))
+ logging.debug(f"match_stdout_to_json_file: stdout={stdout!r}")
+ logging.debug(f"match_stdout_to_json_file: file={obj!r}")
+
+ if isinstance(obj, list):
+ obj = {"key": obj}
+ stdout = {"key": stdout}
+ assert_dict_eq(obj, stdout)
+ elif isinstance(obj, dict):
+ assert_dict_eq(obj, stdout)
+ else:
+ assert_eq(obj, stdout)
+
+
+def manifests_match(ctx, expected=None, actual=None):
+ assert_eq = globals()["assert_eq"]
+ assert_dict_eq = globals()["assert_dict_eq"]
+
+ logging.debug(f"comparing manifests {expected} and {actual}")
+
+ expected_objs = list(yaml.safe_load_all(open(expected)))
+ actual_objs = list(yaml.safe_load_all(open(actual)))
+
+ logging.debug(f"there are {len(expected_objs)} and {len(actual_objs)} objects")
+
+ i = 0
+ while expected_objs and actual_objs:
+ e = expected_objs.pop(0)
+ a = actual_objs.pop(0)
+
+ logging.debug(f"comparing manifest objects at index {i}:")
+ logging.debug(f" expected: {e}")
+ logging.debug(f" actual : {a}")
+ assert_dict_eq(e, a)
+
+ i += 1
+
+ logging.debug(f"remaining expected objecvts: {expected_objs}")
+ logging.debug(f"remaining actual objecvts : {actual_objs}")
+ assert_eq(expected_objs, [])
+ assert_eq(actual_objs, [])
+
+ logging.debug(f"manifests {expected} and {actual} match")
+
+
+def file_is_readable_by_owner(ctx, filename=None):
+ assert_eq = globals()["assert_eq"]
+
+ st = os.lstat(filename)
+ mode = stat.S_IMODE(st.st_mode)
+ logging.debug("file mode: %o", mode)
+ assert_eq(mode, 0o400)
+
+
+def file_does_not_contain(ctx, filename=None, pattern=None):
+ data = open(filename).read()
+ assert pattern not in data
+
+
+def files_are_different(ctx, filename1=None, filename2=None):
+ assert_ne = globals()["assert_ne"]
+
+ data1 = open(filename1, "rb").read()
+ data2 = open(filename2, "rb").read()
+ assert_ne(data1, data2)
+
+
+def files_are_identical(ctx, filename1=None, filename2=None):
+ assert_eq = globals()["assert_eq"]
+
+ data1 = open(filename1, "rb").read()
+ data2 = open(filename2, "rb").read()
+ assert_eq(data1, data2)
diff --git a/subplot/data.yaml b/subplot/data.yaml
index 7659319..533237f 100644
--- a/subplot/data.yaml
+++ b/subplot/data.yaml
@@ -1,25 +1,99 @@
-- given: >
- a file (?P<filename>\\S+) containing "(?P<data>.*)"
- regex: true
- function: create_file_with_given_data
+- given: a file {filename} containing "{data:text}"
+ impl:
+ python:
+ function: create_file_with_given_data
- given: "a file {filename} containing some random data"
- function: create_file_with_random_data
+ impl:
+ python:
+ function: create_file_with_random_data
+
+- given: "a Unix socket {filename}"
+ impl:
+ python:
+ function: create_unix_socket
+
+- given: "a named pipe {filename}"
+ impl:
+ python:
+ function: create_fifo
+
+- given: a cache directory tag in {dirpath}
+ impl:
+ python:
+ function: create_cachedir_tag_in
- given: "a file in {dirname} with a non-UTF8 filename"
- function: create_nonutf8_filename
+ impl:
+ python:
+ function: create_nonutf8_filename
- given: file {filename} has mode {mode}
- function: chmod_file
+ impl:
+ python:
+ function: chmod_file
- given: symbolink link {linkname} that points at {target}
- function: create_symlink
+ impl:
+ python:
+ function: create_symlink
- given: a manifest of the directory {dirname} in {manifest}
- function: create_manifest_of_live
+ impl:
+ python:
+ function: create_manifest_of_live
- given: a manifest of the directory {dirname} restored in {restored} in {manifest}
- function: create_manifest_of_restored
+ impl:
+ python:
+ function: create_manifest_of_restored
+
+- given: "JSON file {json_name} converted from YAML file {yaml_name}"
+ impl:
+ python:
+ function: convert_yaml_to_json
+
+- then: "stdout, as JSON, exactly matches file {filename}"
+ impl:
+ python:
+ function: match_stdout_to_json_file_exactly
+
+- then: "stdout, as JSON, has all the values in file {filename}"
+ impl:
+ python:
+ function: match_stdout_to_json_file_superset
+
+- then: "file {filename} is restored to {restored}"
+ impl:
+ python:
+ function: file_is_restored
+
+- then: "file {filename} is not restored to {restored}"
+ impl:
+ python:
+ function: file_is_not_restored
+
+- then: "manifests {expected} and {actual} match"
+ impl:
+ python:
+ function: manifests_match
+
+- then: "file {filename} is only readable by owner"
+ impl:
+ python:
+ function: file_is_readable_by_owner
+
+- then: "file {filename} does not contain \"{pattern:text}\""
+ impl:
+ python:
+ function: file_does_not_contain
+
+- then: "files {filename1} and {filename2} are different"
+ impl:
+ python:
+ function: files_are_different
-- then: files {first} and {second} match
- function: files_match
+- then: "files {filename1} and {filename2} are identical"
+ impl:
+ python:
+ function: files_are_identical
diff --git a/subplot/server.py b/subplot/server.py
index 289e181..a604733 100644
--- a/subplot/server.py
+++ b/subplot/server.py
@@ -5,8 +5,6 @@ import random
import re
import requests
import shutil
-import socket
-import time
import urllib3
import yaml
@@ -14,7 +12,7 @@ import yaml
urllib3.disable_warnings()
-def start_chunk_server(ctx):
+def start_chunk_server(ctx, env=None):
daemon_start_on_port = globals()["daemon_start_on_port"]
srcdir = globals()["srcdir"]
@@ -35,7 +33,7 @@ def start_chunk_server(ctx):
"address": f"localhost:{port}",
}
- server_binary = os.path.abspath(os.path.join(srcdir, "target", "debug", "obnam-server"))
+ server_binary = ctx["server-binary"]
filename = "config.yaml"
yaml.safe_dump(config, stream=open(filename, "w"))
@@ -44,22 +42,18 @@ def start_chunk_server(ctx):
ctx["server_url"] = f"https://{config['address']}"
daemon_start_on_port(
- ctx,
- name="obnam-server",
- path=server_binary,
- args=filename,
- port=port,
+ ctx, name="obnam-server", path=server_binary, args=filename, port=port, env=env
)
-def stop_chunk_server(ctx):
+def stop_chunk_server(ctx, env=None):
logging.debug("Stopping obnam-server")
daemon_stop = globals()["daemon_stop"]
daemon_stop(ctx, name="obnam-server")
def post_file(ctx, filename=None, path=None, header=None, json=None):
- url = f"{ctx['server_url']}/chunks"
+ url = f"{ctx['server_url']}/v1/chunks"
headers = {header: json}
data = open(filename, "rb").read()
_request(ctx, requests.post, url, headers=headers, data=data)
@@ -71,12 +65,12 @@ def get_chunk_via_var(ctx, var=None):
def get_chunk_by_id(ctx, chunk_id=None):
- url = f"{ctx['server_url']}/chunks/{chunk_id}"
+ url = f"{ctx['server_url']}/v1/chunks/{chunk_id}"
_request(ctx, requests.get, url)
-def find_chunks_with_sha(ctx, sha=None):
- url = f"{ctx['server_url']}/chunks?sha256={sha}"
+def find_chunks_with_label(ctx, sha=None):
+ url = f"{ctx['server_url']}/v1/chunks?label={sha}"
_request(ctx, requests.get, url)
@@ -86,14 +80,16 @@ def delete_chunk_via_var(ctx, var=None):
def delete_chunk_by_id(ctx, chunk_id=None):
- url = f"{ctx['server_url']}/chunks/{chunk_id}"
+ url = f"{ctx['server_url']}/v1/chunks/{chunk_id}"
_request(ctx, requests.delete, url)
def make_chunk_file_be_empty(ctx, chunk_id=None):
chunk_id = ctx["vars"][chunk_id]
chunks = ctx["config"]["chunks"]
- for (dirname, _, _) in os.walk(chunks):
+ logging.debug(f"trying to empty chunk {chunk_id}")
+ for (dirname, _, filenames) in os.walk(chunks):
+ logging.debug(f"found directory {dirname}, with {filenames}")
filename = os.path.join(dirname, chunk_id + ".data")
if os.path.exists(filename):
logging.debug(f"emptying chunk file {filename}")
@@ -138,6 +134,33 @@ def json_body_matches(ctx, wanted=None):
assert_eq(body.get(key, "not.there"), wanted[key])
+def server_has_n_chunks(ctx, n=None):
+ assert_eq = globals()["assert_eq"]
+ n = int(n)
+ files = find_files(ctx["config"]["chunks"])
+ files = [x for x in files if x.endswith(".data")]
+ logging.debug(f"server_has_n_file_chunks: n={n}")
+ logging.debug(f"server_has_n_file_chunks: len(files)={len(files)}")
+ logging.debug(f"server_has_n_file_chunks: files={files}")
+ assert_eq(n, len(files))
+
+
+def server_stderr_contains(ctx, wanted=None):
+ assert_eq = globals()["assert_eq"]
+ assert_eq(_server_stderr_contains(ctx, wanted), True)
+
+
+def server_stderr_doesnt_contain(ctx, wanted=None):
+ assert_eq = globals()["assert_eq"]
+ assert_eq(_server_stderr_contains(ctx, wanted), False)
+
+
+def find_files(root):
+ for dirname, _, names in os.walk(root):
+ for name in names:
+ yield os.path.join(dirname, name)
+
+
# Make an HTTP request.
def _request(ctx, method, url, headers=None, data=None):
r = method(url, headers=headers, data=data, verify=False)
@@ -177,3 +200,17 @@ def _expand_vars(ctx, s):
result.append(value)
s = s[m.end() :]
return "".join(result)
+
+
+def _server_stderr_contains(ctx, wanted):
+ daemon_get_stderr = globals()["daemon_get_stderr"]
+
+ wanted = _expand_vars(ctx, wanted)
+
+ stderr = daemon_get_stderr(ctx, "obnam-server")
+
+ logging.debug(f"_server_stderr_contains:")
+ logging.debug(f" wanted: {wanted}")
+ logging.debug(f" stderr: {stderr}")
+
+ return wanted in stderr
diff --git a/subplot/server.yaml b/subplot/server.yaml
index 68f8f0c..cf57931 100644
--- a/subplot/server.yaml
+++ b/subplot/server.yaml
@@ -1,45 +1,104 @@
- given: "a running chunk server"
- function: start_chunk_server
- cleanup: stop_chunk_server
+ impl:
+ python:
+ function: start_chunk_server
+ cleanup: stop_chunk_server
+
+- given: "a running chunk server with environment {env:text}"
+ impl:
+ python:
+ function: start_chunk_server
+ cleanup: stop_chunk_server
- when: "the chunk server is stopped"
- function: stop_chunk_server
+ impl:
+ python:
+ function: stop_chunk_server
- when: "I POST (?P<filename>\\S+) to (?P<path>\\S+), with (?P<header>\\S+): (?P<json>.*)"
regex: true
- function: post_file
+ types:
+ filename: word
+ path: word
+ header: word
+ json: text
+ impl:
+ python:
+ function: post_file
-- when: "I GET /chunks/<{var}>"
- function: get_chunk_via_var
+- when: "I GET /v1/chunks/<{var}>"
+ impl:
+ python:
+ function: get_chunk_via_var
-- when: "I try to GET /chunks/{chunk_id}"
- function: get_chunk_by_id
+- when: "I try to GET /v1/chunks/{chunk_id}"
+ impl:
+ python:
+ function: get_chunk_by_id
-- when: "I GET /chunks?sha256={sha}"
+- when: "I GET /v1/chunks?label={sha}"
regex: false
- function: find_chunks_with_sha
+ impl:
+ python:
+ function: find_chunks_with_label
-- when: "I DELETE /chunks/<{var}>"
- function: delete_chunk_via_var
+- when: "I DELETE /v1/chunks/<{var}>"
+ impl:
+ python:
+ function: delete_chunk_via_var
-- when: "I try to DELETE /chunks/{chunk_id}"
- function: delete_chunk_by_id
+- when: "I try to DELETE /v1/chunks/{chunk_id}"
+ impl:
+ python:
+ function: delete_chunk_by_id
- when: "chunk <{chunk_id}> on chunk server is replaced by an empty file"
- function: make_chunk_file_be_empty
+ impl:
+ python:
+ function: make_chunk_file_be_empty
- then: "HTTP status code is {status}"
- function: status_code_is
+ impl:
+ python:
+ function: status_code_is
- then: "{header} is {value}"
- function: header_is
+ impl:
+ python:
+ function: header_is
- then: "the JSON body has a field {field}, henceforth {var}"
- function: remember_json_field
+ types:
+ field: word
+ var: word
+ impl:
+ python:
+ function: remember_json_field
- then: "the JSON body matches (?P<wanted>.*)"
regex: true
- function: json_body_matches
+ types:
+ wanted: text
+ impl:
+ python:
+ function: json_body_matches
- then: "the body matches file {filename}"
- function: body_matches_file
+ impl:
+ python:
+ function: body_matches_file
+
+- then: "server has {n:int} chunks"
+ impl:
+ python:
+ function: server_has_n_chunks
+
+- then: chunk server's stderr contains "{wanted:text}"
+ impl:
+ python:
+ function: server_stderr_contains
+
+- then: chunk server's stderr doesn't contain "{wanted:text}"
+ impl:
+ python:
+ function: server_stderr_doesnt_contain
diff --git a/subplot/vendored/daemon.md b/subplot/vendored/daemon.md
deleted file mode 100644
index 131dcb1..0000000
--- a/subplot/vendored/daemon.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Introduction
-
-The [Subplot][] library `daemon` for Python provides scenario steps
-and their implementations for running a background process and
-terminating at the end of the scenario.
-
-[Subplot]: https://subplot.liw.fi/
-
-This document explains the acceptance criteria for the library and how
-they're verified. It uses the steps and functions from the
-`lib/daemon` library. The scenarios all have the same structure: run a
-command, then examine the exit code, verify the process is running.
-
-# Daemon is started and terminated
-
-This scenario starts a background process, verifies it's started, and
-verifies it's terminated after the scenario ends.
-
-~~~scenario
-given there is no "/bin/sleep 12765" process
-when I start "/bin/sleep 12765" as a background process as sleepyhead
-then a process "/bin/sleep 12765" is running
-when I stop background process sleepyhead
-then there is no "/bin/sleep 12765" process
-~~~
-
-
-
----
-title: Acceptance criteria for the lib/daemon Subplot library
-author: The Subplot project
-bindings:
-- daemon.yaml
-template: python
-functions:
-- daemon.py
-- runcmd.py
-...
diff --git a/subplot/vendored/daemon.py b/subplot/vendored/daemon.py
deleted file mode 100644
index febf392..0000000
--- a/subplot/vendored/daemon.py
+++ /dev/null
@@ -1,139 +0,0 @@
-import logging
-import os
-import signal
-import socket
-import subprocess
-import time
-
-
-# Start a daemon that will open a port on localhost.
-def daemon_start_on_port(ctx, path=None, args=None, name=None, port=None):
- daemon_start(ctx, path=path, args=args, name=name)
- daemon_wait_for_port("localhost", port)
-
-
-# Start a daeamon, get its PID. Don't wait for a port or anything. This is
-# meant for background processes that don't have port. Useful for testing the
-# lib/daemon library of Subplot, but not much else.
-def daemon_start(ctx, path=None, args=None, name=None):
- runcmd_run = globals()["runcmd_run"]
- runcmd_exit_code_is = globals()["runcmd_exit_code_is"]
- runcmd_get_exit_code = globals()["runcmd_get_exit_code"]
- runcmd_get_stderr = globals()["runcmd_get_stderr"]
- runcmd_prepend_to_path = globals()["runcmd_prepend_to_path"]
-
- argv = [path] + args.split()
-
- logging.debug(f"Starting daemon {name}")
- logging.debug(f" ctx={ctx.as_dict()}")
- logging.debug(f" name={name}")
- logging.debug(f" path={path}")
- logging.debug(f" args={args}")
- logging.debug(f" argv={argv}")
-
- ns = ctx.declare("_daemon")
-
- this = ns[name] = {
- "pid-file": f"{name}.pid",
- "stderr": f"{name}.stderr",
- "stdout": f"{name}.stdout",
- }
-
- # Debian installs `daemonize` to /usr/sbin, which isn't part of the minimal
- # environment that Subplot sets up. So we add /usr/sbin to the PATH.
- runcmd_prepend_to_path(ctx, "/usr/sbin")
- runcmd_run(
- ctx,
- [
- "daemonize",
- "-c",
- os.getcwd(),
- "-p",
- this["pid-file"],
- "-e",
- this["stderr"],
- "-o",
- this["stdout"],
- ]
- + argv,
- )
-
- # Check that daemonize has exited OK. If it hasn't, it didn't start the
- # background process at all. If so, log the stderr in case there was
- # something useful there for debugging.
- exit = runcmd_get_exit_code(ctx)
- if exit != 0:
- stderr = runcmd_get_stderr(ctx)
- logging.error(f"daemon {name} stderr: {stderr}")
- runcmd_exit_code_is(ctx, 0)
-
- # Get the pid of the background process, from the pid file created by
- # daemonize. We don't need to wait for it, since we know daemonize already
- # exited. If it isn't there now, it's won't appear later.
- if not os.path.exists(this["pid-file"]):
- raise Exception("daemonize didn't create a PID file")
-
- this["pid"] = _daemon_wait_for_pid(this["pid-file"], 10.0)
-
- logging.debug(f"Started daemon {name}")
- logging.debug(f" pid={this['pid']}")
- logging.debug(f" ctx={ctx.as_dict()}")
-
-
-def _daemon_wait_for_pid(filename, timeout):
- start = time.time()
- while time.time() < start + timeout:
- with open(filename) as f:
- data = f.read().strip()
- if data:
- return int(data)
- raise Exception("daemonize created a PID file without a PID")
-
-
-def daemon_wait_for_port(host, port, timeout=3.0):
- addr = (host, port)
- until = time.time() + timeout
- while True:
- try:
- s = socket.create_connection(addr, timeout=timeout)
- s.close()
- return
- except socket.timeout:
- logging.error(f"daemon did not respond at port {port} within {timeout} seconds")
- raise
- except socket.error as e:
- logging.info(f"could not connect to daemon at {port}: {e}")
- pass
- if time.time() >= until:
- logging.error(f"could not connect to daemon at {port} within {timeout} seconds")
- raise ConnectionRefusedError()
-
-
-# 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
-
- logging.debug(f"Terminating process {pid} with signal {signo}")
- try:
- os.kill(pid, signo)
- except ProcessLookupError:
- logging.warning("Process did not actually exist (anymore?)")
-
-
-def daemon_no_such_process(ctx, args=None):
- assert not _daemon_pgrep(args)
-
-
-def daemon_process_exists(ctx, args=None):
- assert _daemon_pgrep(args)
-
-
-def _daemon_pgrep(pattern):
- logging.info(f"checking if process exists: pattern={pattern}")
- exit = subprocess.call(["pgrep", "-laf", pattern])
- logging.info(f"exit code: {exit}")
- return exit == 0
diff --git a/subplot/vendored/daemon.yaml b/subplot/vendored/daemon.yaml
deleted file mode 100644
index 6165c62..0000000
--- a/subplot/vendored/daemon.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-- given: there is no "{args:text}" process
- function: daemon_no_such_process
-
-- when: I start "{path}{args:text}" as a background process as {name}, on port {port}
- function: daemon_start_on_port
-
-- when: I start "{path}{args:text}" as a background process as {name}
- function: daemon_start
-
-- when: I stop background process {name}
- function: daemon_stop
-
-- then: a process "{args:text}" is running
- function: daemon_process_exists
-
-- then: there is no "{args:text}" process
- function: daemon_no_such_process
diff --git a/subplot/vendored/files.md b/subplot/vendored/files.md
deleted file mode 100644
index 68ef1ac..0000000
--- a/subplot/vendored/files.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Introduction
-
-The [Subplot][] library `files` provides scenario steps and their
-implementations for managing files on the file system during tests.
-The library consists of a bindings file `lib/files.yaml` and
-implementations in Python in `lib/files.py`.
-
-[Subplot]: https://subplot.liw.fi/
-
-This document explains the acceptance criteria for the library and how
-they're verified. It uses the steps and functions from the `files`
-library.
-
-# Create on-disk files from embedded files
-
-Subplot allows the source document to embed test files, and the
-`files` library provides steps to create real, on-disk files from
-the embedded files.
-
-~~~scenario
-given file hello.txt
-then file hello.txt exists
-and file hello.txt contains "hello, world"
-and file other.txt does not exist
-given file other.txt from hello.txt
-then file other.txt exists
-and files hello.txt and other.txt match
-and only files hello.txt, other.txt exist
-~~~
-
-~~~{#hello.txt .file .numberLines}
-hello, world
-~~~
-
-
-# File metadata
-
-These steps create files and manage their metadata.
-
-~~~scenario
-given file hello.txt
-when I remember metadata for file hello.txt
-then file hello.txt has same metadata as before
-
-when I write "yo" to file hello.txt
-then file hello.txt has different metadata from before
-~~~
-
-# File modification time
-
-These steps manipulate and test file modification times.
-
-~~~scenario
-given file foo.dat has modification time 1970-01-02 03:04:05
-then file foo.dat has a very old modification time
-
-when I touch file foo.dat
-then file foo.dat has a very recent modification time
-~~~
-
-
-# File contents
-
-These steps verify contents of files.
-
-~~~scenario
-given file hello.txt
-then file hello.txt contains "hello, world"
-and file hello.txt matches regex "hello, .*"
-and file hello.txt matches regex /hello, .*/
-~~~
-
-
----
-title: Acceptance criteria for the files Subplot library
-author: The Subplot project
-template: python
-bindings:
-- files.yaml
-functions:
-- files.py
-...
diff --git a/subplot/vendored/files.py b/subplot/vendored/files.py
deleted file mode 100644
index ec37b9d..0000000
--- a/subplot/vendored/files.py
+++ /dev/null
@@ -1,158 +0,0 @@
-import logging
-import os
-import re
-import time
-
-
-def files_create_from_embedded(ctx, filename=None):
- files_create_from_embedded_with_other_name(
- ctx, filename_on_disk=filename, embedded_filename=filename
- )
-
-
-def files_create_from_embedded_with_other_name(
- ctx, filename_on_disk=None, embedded_filename=None
-):
- get_file = globals()["get_file"]
- with open(filename_on_disk, "wb") as f:
- f.write(get_file(embedded_filename))
-
-
-def files_create_from_text(ctx, filename=None, text=None):
- with open(filename, "w") as f:
- f.write(text)
-
-
-def files_file_exists(ctx, filename=None):
- assert_eq = globals()["assert_eq"]
- assert_eq(os.path.exists(filename), True)
-
-
-def files_file_does_not_exist(ctx, filename=None):
- assert_eq = globals()["assert_eq"]
- assert_eq(os.path.exists(filename), False)
-
-
-def files_only_these_exist(ctx, filenames=None):
- assert_eq = globals()["assert_eq"]
- filenames = filenames.replace(",", "").split()
- assert_eq(set(os.listdir(".")), set(filenames))
-
-
-def files_file_contains(ctx, filename=None, data=None):
- assert_eq = globals()["assert_eq"]
- with open(filename, "rb") as f:
- actual = f.read()
- actual = actual.decode("UTF-8")
- assert_eq(data in actual, True)
-
-
-def files_file_matches_regex(ctx, filename=None, regex=None):
- assert_eq = globals()["assert_eq"]
- with open(filename) as f:
- content = f.read()
- m = re.search(regex, content)
- if m is None:
- logging.debug(f"files_file_matches_regex: no match")
- logging.debug(f" filenamed: {filename}")
- logging.debug(f" regex: {regex}")
- logging.debug(f" content: {regex}")
- logging.debug(f" match: {m}")
- assert_eq(bool(m), True)
-
-
-def files_match(ctx, filename1=None, filename2=None):
- assert_eq = globals()["assert_eq"]
- with open(filename1, "rb") as f:
- data1 = f.read()
- with open(filename2, "rb") as f:
- data2 = f.read()
- assert_eq(data1, data2)
-
-
-def files_touch_with_timestamp(
- ctx,
- filename=None,
- year=None,
- month=None,
- day=None,
- hour=None,
- minute=None,
- second=None,
-):
- t = (
- int(year),
- int(month),
- int(day),
- int(hour),
- int(minute),
- int(second),
- -1,
- -1,
- -1,
- )
- ts = time.mktime(t)
- _files_touch(filename, ts)
-
-
-def files_touch(ctx, filename=None):
- _files_touch(filename, None)
-
-
-def _files_touch(filename, ts):
- if not os.path.exists(filename):
- open(filename, "w").close()
- times = None
- if ts is not None:
- times = (ts, ts)
- os.utime(filename, times=times)
-
-
-def files_mtime_is_recent(ctx, filename=None):
- st = os.stat(filename)
- age = abs(st.st_mtime - time.time())
- assert age < 1.0
-
-
-def files_mtime_is_ancient(ctx, filename=None):
- st = os.stat(filename)
- age = abs(st.st_mtime - time.time())
- year = 365 * 24 * 60 * 60
- required = 39 * year
- logging.debug(f"ancient? mtime={st.st_mtime} age={age} required={required}")
- assert age > required
-
-
-def files_remember_metadata(ctx, filename=None):
- meta = _files_remembered(ctx)
- meta[filename] = _files_get_metadata(filename)
- logging.debug("files_remember_metadata:")
- logging.debug(f" meta: {meta}")
- logging.debug(f" ctx: {ctx}")
-
-
-# Check that current metadata of a file is as stored in the context.
-def files_has_remembered_metadata(ctx, filename=None):
- assert_eq = globals()["assert_eq"]
- meta = _files_remembered(ctx)
- logging.debug("files_has_remembered_metadata:")
- logging.debug(f" meta: {meta}")
- logging.debug(f" ctx: {ctx}")
- assert_eq(meta[filename], _files_get_metadata(filename))
-
-
-def files_has_different_metadata(ctx, filename=None):
- assert_ne = globals()["assert_ne"]
- meta = _files_remembered(ctx)
- assert_ne(meta[filename], _files_get_metadata(filename))
-
-
-def _files_remembered(ctx):
- ns = ctx.declare("_files")
- return ns.get("remembered-metadata", {})
-
-
-def _files_get_metadata(filename):
- st = os.lstat(filename)
- keys = ["st_dev", "st_gid", "st_ino", "st_mode", "st_mtime", "st_size", "st_uid"]
- return {key: getattr(st, key) for key in keys}
diff --git a/subplot/vendored/files.yaml b/subplot/vendored/files.yaml
deleted file mode 100644
index be69920..0000000
--- a/subplot/vendored/files.yaml
+++ /dev/null
@@ -1,62 +0,0 @@
-- given: file {filename}
- function: files_create_from_embedded
- types:
- filename: file
-
-- given: file {filename_on_disk} from {embedded_filename}
- function: files_create_from_embedded_with_other_name
- types:
- embedded_filename: file
-
-- given: file {filename} has modification time {year}-{month}-{day} {hour}:{minute}:{second}
- function: files_touch_with_timestamp
-
-- when: I write "(?P<text>.*)" to file (?P<filename>\S+)
- regex: true
- function: files_create_from_text
-
-- when: I remember metadata for file {filename}
- function: files_remember_metadata
-
-- when: I touch file {filename}
- function: files_touch
-
-- then: file {filename} exists
- function: files_file_exists
-
-- then: file {filename} does not exist
- function: files_file_does_not_exist
-
-- then: only files (?P<filenames>.+) exist
- function: files_only_these_exist
- regex: true
-
-- then: file (?P<filename>\S+) contains "(?P<data>.*)"
- regex: true
- function: files_file_contains
-
-- then: file (?P<filename>\S+) matches regex /(?P<regex>.*)/
- regex: true
- function: files_file_matches_regex
-
-- then: file (?P<filename>\S+) matches regex "(?P<regex>.*)"
- regex: true
- function: files_file_matches_regex
-
-- then: files {filename1} and {filename2} match
- function: files_match
-
-- then: file {filename} has same metadata as before
- function: files_has_remembered_metadata
-
-- then: file {filename} has different metadata from before
- function: files_has_different_metadata
-
-- then: file {filename} has changed from before
- function: files_has_different_metadata
-
-- then: file {filename} has a very recent modification time
- function: files_mtime_is_recent
-
-- then: file {filename} has a very old modification time
- function: files_mtime_is_ancient
diff --git a/subplot/vendored/runcmd.md b/subplot/vendored/runcmd.md
deleted file mode 100644
index a9d4ed4..0000000
--- a/subplot/vendored/runcmd.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Introduction
-
-The [Subplot][] library `runcmd` for Python provides scenario steps
-and their implementations for running Unix commands and examining the
-results. The library consists of a bindings file `lib/runcmd.yaml` and
-implementations in Python in `lib/runcmd.py`. There is no Bash
-version.
-
-[Subplot]: https://subplot.liw.fi/
-
-This document explains the acceptance criteria for the library and how
-they're verified. It uses the steps and functions from the
-`lib/runcmd` library. The scenarios all have the same structure: run a
-command, then examine the exit code, standard output (stdout for
-short), or standard error output (stderr) of the command.
-
-The scenarios use the Unix commands `/bin/true` and `/bin/false` to
-generate exit codes, and `/bin/echo` to produce stdout. To generate
-stderr, they use the little helper script below.
-
-~~~{#err.sh .file .sh .numberLines}
-#!/bin/sh
-echo "$@" 1>&2
-~~~
-
-# Check exit code
-
-These scenarios verify the exit code. To make it easier to write
-scenarios in language that flows more naturally, there are a couple of
-variations.
-
-## Successful execution
-
-~~~scenario
-when I run /bin/true
-then exit code is 0
-and command is successful
-~~~
-
-## Failed execution
-
-~~~scenario
-when I try to run /bin/false
-then exit code is not 0
-and command fails
-~~~
-
-# Check output has what we want
-
-These scenarios verify that stdout or stderr do have something we want
-to have.
-
-## Check stdout is exactly as wanted
-
-Note that the string is surrounded by double quotes to make it clear
-to the reader what's inside. Also, C-style string escapes are
-understood.
-
-~~~scenario
-when I run /bin/echo hello, world
-then stdout is exactly "hello, world\n"
-~~~
-
-## Check stderr is exactly as wanted
-
-~~~scenario
-given helper script err.sh for runcmd
-when I run sh err.sh hello, world
-then stderr is exactly "hello, world\n"
-~~~
-
-## Check stdout using sub-string search
-
-Exact string comparisons are not always enough, so we can verify a
-sub-string is in output.
-
-~~~scenario
-when I run /bin/echo hello, world
-then stdout contains "world\n"
-and exit code is 0
-~~~
-
-## Check stderr using sub-string search
-
-~~~scenario
-given helper script err.sh for runcmd
-when I run sh err.sh hello, world
-then stderr contains "world\n"
-~~~
-
-## Check stdout using regular expressions
-
-Fixed strings are not always enough, so we can verify output matches a
-regular expression. Note that the regular expression is not delimited
-and does not get any C-style string escaped decoded.
-
-~~~scenario
-when I run /bin/echo hello, world
-then stdout matches regex world$
-~~~
-
-## Check stderr using regular expressions
-
-~~~scenario
-given helper script err.sh for runcmd
-when I run sh err.sh hello, world
-then stderr matches regex world$
-~~~
-
-# Check output doesn't have what we want to avoid
-
-These scenarios verify that the stdout or stderr do not
-have something we want to avoid.
-
-## Check stdout is not exactly something
-
-~~~scenario
-when I run /bin/echo hi
-then stdout isn't exactly "hello, world\n"
-~~~
-
-## Check stderr is not exactly something
-
-~~~scenario
-given helper script err.sh for runcmd
-when I run sh err.sh hi
-then stderr isn't exactly "hello, world\n"
-~~~
-
-## Check stdout doesn't contain sub-string
-
-~~~scenario
-when I run /bin/echo hi
-then stdout doesn't contain "world"
-~~~
-
-## Check stderr doesn't contain sub-string
-
-~~~scenario
-given helper script err.sh for runcmd
-when I run sh err.sh hi
-then stderr doesn't contain "world"
-~~~
-
-## Check stdout doesn't match regular expression
-
-~~~scenario
-when I run /bin/echo hi
-then stdout doesn't match regex world$
-
-~~~
-
-## Check stderr doesn't match regular expressions
-
-~~~scenario
-given helper script err.sh for runcmd
-when I run sh err.sh hi
-then stderr doesn't match regex world$
-~~~
-
-
----
-title: Acceptance criteria for the lib/runcmd Subplot library
-author: The Subplot project
-template: python
-bindings:
-- runcmd.yaml
-functions:
-- runcmd.py
-...
diff --git a/subplot/vendored/runcmd.py b/subplot/vendored/runcmd.py
deleted file mode 100644
index a2564c6..0000000
--- a/subplot/vendored/runcmd.py
+++ /dev/null
@@ -1,252 +0,0 @@
-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")
-
- # The Subplot Python template empties os.environ at startup, modulo a small
- # number of variables with carefully chosen values. Here, we don't need to
- # care about what those variables are, but we do need to not overwrite
- # them, so we just add anything in the env keyword argument, if any, to
- # os.environ.
- env = dict(os.environ)
- for key, arg in kwargs.pop("env", {}).items():
- env[key] = arg
-
- 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/vendored/runcmd.yaml b/subplot/vendored/runcmd.yaml
deleted file mode 100644
index 48dde90..0000000
--- a/subplot/vendored/runcmd.yaml
+++ /dev/null
@@ -1,83 +0,0 @@
-# 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