diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-09-13 09:44:39 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-09-13 09:44:39 +0000 |
commit | a06ef1f46c8603b8b5e5a44e6521b10238331891 (patch) | |
tree | 995392da4d56597be9ac71bba4669df299506ea7 /share | |
parent | 217d920d72d9bf56174e0694329ef9c558160b69 (diff) | |
parent | 66bedaf70e7b652f791dd0e2fcbd39db3cbec6f9 (diff) | |
download | subplot-a06ef1f46c8603b8b5e5a44e6521b10238331891.tar.gz |
Merge branch 'multi-lang-docs' into 'main'
Implement polyglot bindings
See merge request subplot/subplot!210
Diffstat (limited to 'share')
-rw-r--r-- | share/common/lib/files.yaml | 188 | ||||
-rw-r--r-- | share/common/lib/runcmd.yaml | 185 | ||||
-rw-r--r-- | share/python/lib/daemon.md | 189 | ||||
-rw-r--r-- | share/python/lib/daemon.yaml | 77 | ||||
-rw-r--r-- | share/python/lib/files.md | 105 | ||||
-rw-r--r-- | share/python/lib/files.py | 35 | ||||
-rw-r--r-- | share/python/lib/files.yaml | 83 | ||||
-rw-r--r-- | share/python/lib/runcmd.md | 214 | ||||
-rw-r--r-- | share/python/lib/runcmd.py | 6 | ||||
-rw-r--r-- | share/python/lib/runcmd.yaml | 91 | ||||
-rw-r--r-- | share/python/lib/runcmd_test.py | 15 | ||||
-rw-r--r-- | share/python/lib/runcmd_test.yaml | 5 | ||||
-rw-r--r-- | share/rust/lib/datadir.yaml | 8 | ||||
-rw-r--r-- | share/rust/lib/files.yaml | 89 | ||||
-rw-r--r-- | share/rust/lib/runcmd.yaml | 97 |
15 files changed, 447 insertions, 940 deletions
diff --git a/share/common/lib/files.yaml b/share/common/lib/files.yaml new file mode 100644 index 0000000..40c092a --- /dev/null +++ b/share/common/lib/files.yaml @@ -0,0 +1,188 @@ +# Bindings for the files steps + +- given: file {embedded_file} + impl: + rust: + function: subplotlib::steplibrary::files::create_from_embedded + python: + function: files_create_from_embedded + types: + embedded_file: file + +- given: file {filename_on_disk} from {embedded_file} + impl: + rust: + function: subplotlib::steplibrary::files::create_from_embedded_with_other_name + python: + function: files_create_from_embedded_with_other_name + types: + embedded_file: file + +- given: file (?P<filename>\S+) has modification time (?P<mtime>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) + regex: true + impl: + rust: + function: subplotlib::steplibrary::files::touch_with_timestamp + python: + function: files_touch_with_timestamp + types: + mtime: text + +- when: I write "(?P<text>.*)" to file (?P<filename>\S+) + regex: true + impl: + rust: + function: subplotlib::steplibrary::files::create_from_text + python: + function: files_create_from_text + +- when: I remember metadata for file {filename} + impl: + rust: + function: subplotlib::steplibrary::files::remember_metadata + python: + function: files_remember_metadata + +- when: I touch file {filename} + impl: + rust: + function: subplotlib::steplibrary::files::touch + python: + function: files_touch + +- then: file {filename} exists + impl: + rust: + function: subplotlib::steplibrary::files::file_exists + python: + function: files_file_exists + +- then: file {filename} does not exist + impl: + rust: + function: subplotlib::steplibrary::files::file_does_not_exist + python: + function: files_file_does_not_exist + +- then: only files (?P<filenames>.+) exist + impl: + rust: + function: subplotlib::steplibrary::files::only_these_exist + python: + function: files_only_these_exist + regex: true + +- then: file (?P<filename>\S+) contains "(?P<data>.*)" + regex: true + impl: + rust: + function: subplotlib::steplibrary::files::file_contains + python: + function: files_file_contains + +- then: file (?P<filename>\S+) matches regex /(?P<regex>.*)/ + regex: true + impl: + rust: + function: subplotlib::steplibrary::files::file_matches_regex + python: + function: files_file_matches_regex + +- then: file (?P<filename>\S+) matches regex "(?P<regex>.*)" + regex: true + impl: + rust: + function: subplotlib::steplibrary::files::file_matches_regex + python: + function: files_file_matches_regex + +- then: files {filename1} and {filename2} match + impl: + rust: + function: subplotlib::steplibrary::files::file_match + python: + function: files_match + +- then: file {filename} has same metadata as before + impl: + rust: + function: subplotlib::steplibrary::files::has_remembered_metadata + python: + function: files_has_remembered_metadata + +- then: file {filename} has different metadata from before + impl: + rust: + function: subplotlib::steplibrary::files::has_different_metadata + python: + function: files_has_different_metadata + +- then: file {filename} has changed from before + impl: + rust: + function: subplotlib::steplibrary::files::has_different_metadata + python: + function: files_has_different_metadata + +- then: file {filename} has a very recent modification time + impl: + rust: + function: subplotlib::steplibrary::files::mtime_is_recent + python: + function: files_mtime_is_recent + +- then: file {filename} has a very old modification time + impl: + rust: + function: subplotlib::steplibrary::files::mtime_is_ancient + python: + function: files_mtime_is_ancient + +- given: a directory {path} + impl: + rust: + function: subplotlib::steplibrary::files::make_directory + python: + function: files_make_directory + +- when: I create directory {path} + impl: + rust: + function: subplotlib::steplibrary::files::make_directory + python: + function: files_make_directory + +- when: I remove directory {path} + impl: + rust: + function: subplotlib::steplibrary::files::remove_directory + python: + function: files_remove_directory + +- then: directory {path} exists + impl: + rust: + function: subplotlib::steplibrary::files::path_exists + python: + function: files_directory_exists + +- then: directory {path} does not exist + impl: + rust: + function: subplotlib::steplibrary::files::path_does_not_exist + python: + function: files_directory_does_not_exist + +- then: directory {path} is empty + impl: + rust: + function: subplotlib::steplibrary::files::path_is_empty + python: + function: files_directory_is_empty + +- then: directory {path} is not empty + impl: + rust: + function: subplotlib::steplibrary::files::path_is_not_empty + python: + function: files_directory_is_not_empty diff --git a/share/common/lib/runcmd.yaml b/share/common/lib/runcmd.yaml new file mode 100644 index 0000000..bde2f69 --- /dev/null +++ b/share/common/lib/runcmd.yaml @@ -0,0 +1,185 @@ +# Bindings for the runcmd steplibrary + +- given: helper script {script} for runcmd + impl: + python: + function: runcmd_helper_script + rust: + function: subplotlib::steplibrary::runcmd::helper_script + types: + script: file + +- given: srcdir is in the PATH + impl: + python: + function: runcmd_helper_srcdir_path + rust: + function: subplotlib::steplibrary::runcmd::helper_srcdir_path + +- when: I run (?P<argv0>\S+)(?P<args>.*) + regex: true + impl: + python: + function: runcmd_step + rust: + function: subplotlib::steplibrary::runcmd::run + +- when: I run, in (?P<dirname>\S+), (?P<argv0>\S+)(?P<args>.*) + regex: true + impl: + python: + function: runcmd_step_in + rust: + function: subplotlib::steplibrary::runcmd::run_in + +- when: I try to run (?P<argv0>\S+)(?P<args>.*) + regex: true + impl: + python: + function: runcmd_try_to_run + rust: + function: subplotlib::steplibrary::runcmd::try_to_run + +- when: I try to run, in (?P<dirname>\S+), (?P<argv0>\S+)(?P<args>.*) + regex: true + impl: + python: + function: runcmd_try_to_run_in + rust: + function: subplotlib::steplibrary::runcmd::try_to_run_in + +# Steps to examine exit code of latest command. + +- then: exit code is {exit} + impl: + python: + function: runcmd_exit_code_is + rust: + function: subplotlib::steplibrary::runcmd::exit_code_is + types: + exit: int + +- then: exit code is not {exit} + impl: + python: + function: runcmd_exit_code_is_not + rust: + function: subplotlib::steplibrary::runcmd::exit_code_is_not + types: + exit: int + +- then: command is successful + impl: + python: + function: runcmd_exit_code_is_zero + rust: + function: subplotlib::steplibrary::runcmd::exit_code_is_zero + +- then: command fails + impl: + python: + function: runcmd_exit_code_is_nonzero + rust: + function: subplotlib::steplibrary::runcmd::exit_code_is_nonzero + +# Steps to examine stdout/stderr for exact content. + +- then: stdout is exactly "(?P<text>.*)" + regex: true + impl: + python: + function: runcmd_stdout_is + rust: + function: subplotlib::steplibrary::runcmd::stdout_is + +- then: 'stdout isn''t exactly "(?P<text>.*)"' + regex: true + impl: + python: + function: runcmd_stdout_isnt + rust: + function: subplotlib::steplibrary::runcmd::stdout_isnt + +- then: stderr is exactly "(?P<text>.*)" + regex: true + impl: + python: + function: runcmd_stderr_is + rust: + function: subplotlib::steplibrary::runcmd::stderr_is + +- then: 'stderr isn''t exactly "(?P<text>.*)"' + regex: true + impl: + python: + function: runcmd_stderr_isnt + rust: + function: subplotlib::steplibrary::runcmd::stderr_isnt + +# Steps to examine stdout/stderr for sub-strings. + +- then: stdout contains "(?P<text>.*)" + regex: true + impl: + python: + function: runcmd_stdout_contains + rust: + function: subplotlib::steplibrary::runcmd::stdout_contains + +- then: 'stdout doesn''t contain "(?P<text>.*)"' + regex: true + impl: + python: + function: runcmd_stdout_doesnt_contain + rust: + function: subplotlib::steplibrary::runcmd::stdout_doesnt_contain + +- then: stderr contains "(?P<text>.*)" + regex: true + impl: + python: + function: runcmd_stderr_contains + rust: + function: subplotlib::steplibrary::runcmd::stderr_contains + +- then: 'stderr doesn''t contain "(?P<text>.*)"' + regex: true + impl: + python: + function: runcmd_stderr_doesnt_contain + rust: + function: subplotlib::steplibrary::runcmd::stderr_doesnt_contain + +# Steps to match stdout/stderr against regular expressions. + +- then: stdout matches regex (?P<regex>.*) + regex: true + impl: + python: + function: runcmd_stdout_matches_regex + rust: + function: subplotlib::steplibrary::runcmd::stdout_matches_regex + +- then: stdout doesn't match regex (?P<regex>.*) + regex: true + impl: + python: + function: runcmd_stdout_doesnt_match_regex + rust: + function: subplotlib::steplibrary::runcmd::stdout_doesnt_match_regex + +- then: stderr matches regex (?P<regex>.*) + regex: true + impl: + python: + function: runcmd_stderr_matches_regex + rust: + function: subplotlib::steplibrary::runcmd::stderr_matches_regex + +- then: stderr doesn't match regex (?P<regex>.*) + regex: true + impl: + python: + function: runcmd_stderr_doesnt_match_regex + rust: + function: subplotlib::steplibrary::runcmd::stderr_doesnt_match_regex diff --git a/share/python/lib/daemon.md b/share/python/lib/daemon.md deleted file mode 100644 index c7bb49f..0000000 --- a/share/python/lib/daemon.md +++ /dev/null @@ -1,189 +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 -~~~ - - -# Daemon takes a while to open its port - -This scenario verifies that if the background process doesn't immediately start -listening on its port, the daemon library handles that correctly. We do this -with a helper script that waits 2 seconds before opening the port. The -lib/daemon code will wait for the script by repeatedly trying to connect. Once -successful, it immediately closes the port, which causes the script to -terminate. - -~~~scenario -given a daemon helper shell script slow-start-daemon.py -given there is no "slow-start-daemon.py" process -when I try to start "./slow-start-daemon.py" as slow-daemon, on port 8888 -then starting the daemon succeeds -when I stop background process slow-daemon -then there is no "slow-start-daemon.py" process -~~~ - -~~~{#slow-start-daemon.py .file .python .numberLines} -#!/usr/bin/env python3 - -import socket -import time - -time.sleep(2) - -s = socket.socket() -s.bind(("127.0.0.1", 8888)) -s.listen() - -(conn, _) = s.accept() -conn.recv(1) -s.close() - -print("OK") -~~~ - -# Daemon never opens the intended port - -This scenario verifies that if the background process never starts -listening on its port, the daemon library handles that correctly. - -~~~scenario -given there is no "/bin/sleep 12765" process -when I try to start "/bin/sleep 12765" as sleepyhead, on port 8888 -then starting daemon fails with "ConnectionRefusedError" -then a process "/bin/sleep 12765" is running -when I stop background process sleepyhead -then there is no "/bin/sleep 12765" process -~~~ - - -# Daemon stdout and stderr are retrievable - -Sometimes it's useful for the step functions to be able to retrieve -the stdout or stderr of of the daemon, after it's started, or even -after it's terminated. This scenario verifies that `lib/daemon` can do -that. - -~~~scenario -given a daemon helper shell script chatty-daemon.sh -given there is no "chatty-daemon" process -when I start "./chatty-daemon.sh" as a background process as chatty-daemon -when daemon chatty-daemon has produced output -when I stop background process chatty-daemon -then there is no "chatty-daemon" process -then daemon chatty-daemon stdout is "hi there\n" -then daemon chatty-daemon stderr is "hola\n" -~~~ - -We make for the daemon to exit, to work around a race condition: if -the test program retrieves the daemon's output too fast, it may not -have had time to produce it yet. - - -~~~{#chatty-daemon.sh .file .sh .numberLines} -#!/bin/bash - -set -euo pipefail - -trap 'exit 0' TERM - -echo hola 1>&2 -echo hi there -~~~ - -# Can specify additional environment variables for daemon - -Some daemons are configured through their environment rather than configuration -files. This scenario verifies that a step can set arbitrary variables in the -daemon's environment. - -~~~scenario -when I start "/usr/bin/env" as a background process as env, with environment {"custom_variable": "has a Value"} -when daemon env has produced output -when I stop background process env -then daemon env stdout contains "custom_variable=has a Value" -~~~ - -~~~scenario -given a daemon helper shell script env-with-port.py -when I try to start "./env-with-port.py 8765" as env-with-port, on port 8765, with environment {"custom_variable": "1337"} -when I stop background process env-with-port -then daemon env-with-port stdout contains "custom_variable=1337" -~~~ - -~~~scenario -given a daemon helper shell script env-with-port.py -when I start "./env-with-port.py 8766" as a background process as another-env-with-port, on port 8766, with environment {"subplot2": "000"} -when daemon another-env-with-port has produced output -when I stop background process another-env-with-port -then daemon another-env-with-port stdout contains "subplot2=000" -~~~ - -It's important that these new environment variables are not inherited by the -steps that follow. To verify that, we run one more scenario which *doesn't* set -any variables, but checks that none of the variables we mentioned above are -present. - -~~~scenario -when I start "/usr/bin/env" as a background process as env2 -when daemon env2 has produced output -when I stop background process env2 -then daemon env2 stdout doesn't contain "custom_variable=has a Value" -then daemon env2 stdout doesn't contain "custom_variable=1337" -then daemon env2 stdout doesn't contain "subplot2=000" -~~~ - -~~~{#env-with-port.py .file .python .numberLines} -#!/usr/bin/env python3 - -import os -import socket -import sys -import time - -for (key, value) in os.environ.items(): - print(f"{key}={value}") - -port = int(sys.argv[1]) -print(f"port is {port}") - -s = socket.socket() -s.bind(("127.0.0.1", port)) -s.listen() - -(conn, _) = s.accept() -conn.recv(1) -s.close() -~~~ - - ---- -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/share/python/lib/daemon.yaml b/share/python/lib/daemon.yaml index f4a2f86..5cbc7d0 100644 --- a/share/python/lib/daemon.yaml +++ b/share/python/lib/daemon.yaml @@ -1,59 +1,94 @@ - given: there is no "{args:text}" process - function: daemon_no_such_process + impl: + python: + function: daemon_no_such_process - given: a daemon helper shell script {filename} - function: _daemon_shell_script + impl: + python: + function: _daemon_shell_script - when: I start "{path}{args:text}" as a background process as {name}, on port {port} - function: daemon_start_on_port + impl: + python: + function: daemon_start_on_port - when: I start "(?P<path>[^ "]+)(?P<args>[^"]*)" as a background process as (?P<name>[^,]+), on port (?P<port>\d+), with environment (?P<env>.*) regex: true - function: daemon_start_on_port + impl: + python: + function: daemon_start_on_port - when: I try to start "{path}{args:text}" as {name}, on port {port} - function: _daemon_start_soonish - cleanup: _daemon_stop_soonish + impl: + python: + function: _daemon_start_soonish + cleanup: _daemon_stop_soonish - when: I try to start "(?P<path>[^ "]+)(?P<args>[^"]*)" as (?P<name>[^,]+), on port (?P<port>\d+), with environment (?P<env>.*) regex: true - function: _daemon_start_soonish - cleanup: _daemon_stop_soonish + impl: + python: + function: _daemon_start_soonish + cleanup: _daemon_stop_soonish - when: I start "{path}{args:text}" as a background process as {name} - function: _daemon_start + impl: + python: + function: _daemon_start - when: I start "(?P<path>[^ "]+)(?P<args>[^"]*)" as a background process as (?P<name>[^,]+), with environment (?P<env>.*) regex: true - function: _daemon_start + impl: + python: + function: _daemon_start - when: I stop background process {name} - function: daemon_stop + impl: + python: + function: daemon_stop - when: daemon {name} has produced output - function: daemon_has_produced_output + impl: + python: + function: daemon_has_produced_output - then: a process "{args:text}" is running - function: daemon_process_exists + impl: + python: + function: daemon_process_exists - then: there is no "{args:text}" process - function: daemon_no_such_process + impl: + python: + function: daemon_no_such_process - then: starting daemon fails with "{message:text}" - function: daemon_start_fails_with + impl: + python: + function: daemon_start_fails_with - then: starting the daemon succeeds - function: daemon_start_succeeds + impl: + python: + function: daemon_start_succeeds - then: daemon {name} stdout is "{text:text}" - function: daemon_stdout_is + impl: + python: + function: daemon_stdout_is - then: daemon {name} stdout contains "{text:text}" - function: daemon_stdout_contains + impl: + python: + function: daemon_stdout_contains - then: daemon {name} stdout doesn't contain "{text:text}" - function: daemon_stdout_doesnt_contain + impl: + python: + function: daemon_stdout_doesnt_contain - then: daemon {name} stderr is "{text:text}" - function: daemon_stderr_is - + impl: + python: + function: daemon_stderr_is diff --git a/share/python/lib/files.md b/share/python/lib/files.md deleted file mode 100644 index 823c760..0000000 --- a/share/python/lib/files.md +++ /dev/null @@ -1,105 +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, .*/ -~~~ - -# Directories - -There are also a large number of directory based steps and some directory -based behaviour available in creating files which are available in the files -library. - -```scenario -given a directory first -then directory first exists -and directory first is empty -and directory second does not exist -when I remove directory first -then directory first does not exist -when I create directory second -then directory second exists -and directory second is empty -given file second/third/hello.txt from hello.txt -then directory second is not empty -and directory second/third exists -and directory second/third is not empty -when I remove directory second -then directory second does not exist -``` - ---- -title: Acceptance criteria for the files Subplot library -author: The Subplot project -template: python -bindings: -- files.yaml -functions: -- files.py -... diff --git a/share/python/lib/files.py b/share/python/lib/files.py index fa219b4..8c2c3a7 100644 --- a/share/python/lib/files.py +++ b/share/python/lib/files.py @@ -1,3 +1,5 @@ +from time import strptime + import logging import os import re @@ -5,21 +7,21 @@ import shutil import time -def files_create_from_embedded(ctx, filename=None): - files_make_directory(ctx, path=os.path.dirname(filename) or ".") +def files_create_from_embedded(ctx, embedded_file=None): + files_make_directory(ctx, path=os.path.dirname(embedded_file) or ".") files_create_from_embedded_with_other_name( - ctx, filename_on_disk=filename, embedded_filename=filename + ctx, filename_on_disk=embedded_file, embedded_file=embedded_file ) def files_create_from_embedded_with_other_name( - ctx, filename_on_disk=None, embedded_filename=None + ctx, filename_on_disk=None, embedded_file=None ): get_file = globals()["get_file"] files_make_directory(ctx, path=os.path.dirname(filename_on_disk) or ".") with open(filename_on_disk, "wb") as f: - f.write(get_file(embedded_filename)) + f.write(get_file(embedded_file)) def files_create_from_text(ctx, filename=None, text=None): @@ -106,27 +108,8 @@ def files_match(ctx, filename1=None, filename2=None): 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, - ) +def files_touch_with_timestamp(ctx, filename=None, mtime = None): + t = strptime(mtime, "%Y-%m-%d %H:%M:%S") ts = time.mktime(t) _files_touch(filename, ts) diff --git a/share/python/lib/files.yaml b/share/python/lib/files.yaml deleted file mode 100644 index f18b8cd..0000000 --- a/share/python/lib/files.yaml +++ /dev/null @@ -1,83 +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 - -- given: a directory {path} - function: files_make_directory - -- when: I create directory {path} - function: files_make_directory - -- when: I remove directory {path} - function: files_remove_directory - -- then: directory {path} exists - function: files_directory_exists - -- then: directory {path} does not exist - function: files_directory_does_not_exist - -- then: directory {path} is empty - function: files_directory_is_empty - -- then: directory {path} is not empty - function: files_directory_is_not_empty diff --git a/share/python/lib/runcmd.md b/share/python/lib/runcmd.md deleted file mode 100644 index 5f99b2a..0000000 --- a/share/python/lib/runcmd.md +++ /dev/null @@ -1,214 +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 -~~~ - -## Successful execution in a sub-directory - -~~~scenario -given a directory xyzzy -when I run, in xyzzy, /bin/pwd -then exit code is 0 -then command is successful -then stdout contains "/xyzzy" -~~~ - -## Failed execution - -~~~scenario -when I try to run /bin/false -then exit code is not 0 -and command fails -~~~ - -## Failed execution in a sub-directory - -~~~scenario -given a directory xyzzy -when I try to run, in xyzzy, /bin/false -then exit code is not 0 -and command fails -~~~ - -# Check we can prepend to $PATH - -This scenario verifies that we can add a directory to the beginning of -the PATH environment variable, so that we can have `runcmd` invoke a -binary from our build tree rather than from system directories. This -is especially useful for testing new versions of software that's -already installed on the system. - -~~~scenario -given executable script ls from ls.sh -when I prepend . to PATH -when I run ls -then command is successful -then stdout contains "custom ls, not system ls" -~~~ - -~~~{#ls.sh .file .sh .numberLines} -#!/bin/sh -echo "custom ls, not system ls" -~~~ - -# 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 -- runcmd_test.yaml -- files.yaml -functions: -- runcmd.py -- runcmd_test.py -- files.py -... diff --git a/share/python/lib/runcmd.py b/share/python/lib/runcmd.py index da6c3fd..c4a6a12 100644 --- a/share/python/lib/runcmd.py +++ b/share/python/lib/runcmd.py @@ -93,10 +93,10 @@ def runcmd_helper_srcdir_path(ctx): # Step: This creates a helper script. -def runcmd_helper_script(ctx, filename=None): +def runcmd_helper_script(ctx, script=None): get_file = globals()["get_file"] - with open(filename, "wb") as f: - f.write(get_file(filename)) + with open(script, "wb") as f: + f.write(get_file(script)) # diff --git a/share/python/lib/runcmd.yaml b/share/python/lib/runcmd.yaml deleted file mode 100644 index a5119d8..0000000 --- a/share/python/lib/runcmd.yaml +++ /dev/null @@ -1,91 +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 run, in (?P<dirname>\S+), (?P<argv0>\S+)(?P<args>.*) - regex: true - function: runcmd_step_in - -- when: I try to run (?P<argv0>\S+)(?P<args>.*) - regex: true - function: runcmd_try_to_run - -- when: I try to run, in (?P<dirname>\S+), (?P<argv0>\S+)(?P<args>.*) - regex: true - function: runcmd_try_to_run_in - -# 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 diff --git a/share/python/lib/runcmd_test.py b/share/python/lib/runcmd_test.py deleted file mode 100644 index 6aa435a..0000000 --- a/share/python/lib/runcmd_test.py +++ /dev/null @@ -1,15 +0,0 @@ -import os - - -def create_script_from_embedded(ctx, filename=None, embedded=None): - files_create_from_embedded_with_other_name = globals()[ - "files_create_from_embedded_with_other_name" - ] - - # Create the file. - files_create_from_embedded_with_other_name( - ctx, filename_on_disk=filename, embedded_filename=embedded - ) - - # Make the new file executable. - os.chmod(filename, 0o755) diff --git a/share/python/lib/runcmd_test.yaml b/share/python/lib/runcmd_test.yaml deleted file mode 100644 index 8ade220..0000000 --- a/share/python/lib/runcmd_test.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- given: "executable script {filename} from {embedded}" - function: create_script_from_embedded - -- when: "I prepend {dirname} to PATH" - function: runcmd_prepend_to_path diff --git a/share/rust/lib/datadir.yaml b/share/rust/lib/datadir.yaml index acd4ad4..f4c313b 100644 --- a/share/rust/lib/datadir.yaml +++ b/share/rust/lib/datadir.yaml @@ -4,10 +4,14 @@ # of the capabilities are worth exporting as steps - given: datadir has at least {bytes}B of space - function: subplotlib::steplibrary::datadir::datadir_has_enough_space + impl: + rust: + function: subplotlib::steplibrary::datadir::datadir_has_enough_space types: bytes: uint - given: datadir has at least {megabytes}M of space - function: subplotlib::steplibrary::datadir::datadir_has_enough_space_megabytes + impl: + rust: + function: subplotlib::steplibrary::datadir::datadir_has_enough_space_megabytes types: megabytes: uint diff --git a/share/rust/lib/files.yaml b/share/rust/lib/files.yaml deleted file mode 100644 index 5fa4f45..0000000 --- a/share/rust/lib/files.yaml +++ /dev/null @@ -1,89 +0,0 @@ -# Bindings for the files steps -# These bind the files step library for subplotlib - -- given: file {embedded_file} - function: subplotlib::steplibrary::files::create_from_embedded - types: - embedded_file: file - -- given: file {filename_on_disk} from {embedded_file} - function: subplotlib::steplibrary::files::create_from_embedded_with_other_name - types: - embedded_file: file - -- given: file (?P<filename>\S+) has modification time (?P<mtime>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) - regex: true - function: subplotlib::steplibrary::files::touch_with_timestamp - types: - mtime: text - -- when: I write "(?P<text>.*)" to file (?P<filename>\S+) - regex: true - function: subplotlib::steplibrary::files::create_from_text - -- when: I remember metadata for file {filename} - function: subplotlib::steplibrary::files::remember_metadata - -- when: I touch file {filename} - function: subplotlib::steplibrary::files::touch - -- then: file {filename} exists - function: subplotlib::steplibrary::files::file_exists - -- then: file {filename} does not exist - function: subplotlib::steplibrary::files::file_does_not_exist - -- then: only files (?P<filenames>.+) exist - function: subplotlib::steplibrary::files::only_these_exist - regex: true - -- then: file (?P<filename>\S+) contains "(?P<data>.*)" - regex: true - function: subplotlib::steplibrary::files::file_contains - -- then: file (?P<filename>\S+) matches regex /(?P<regex>.*)/ - regex: true - function: subplotlib::steplibrary::files::file_matches_regex - -- then: file (?P<filename>\S+) matches regex "(?P<regex>.*)" - regex: true - function: subplotlib::steplibrary::files::file_matches_regex - -- then: files {filename1} and {filename2} match - function: subplotlib::steplibrary::files::file_match - -- then: file {filename} has same metadata as before - function: subplotlib::steplibrary::files::has_remembered_metadata - -- then: file {filename} has different metadata from before - function: subplotlib::steplibrary::files::has_different_metadata - -- then: file {filename} has changed from before - function: subplotlib::steplibrary::files::has_different_metadata - -- then: file {filename} has a very recent modification time - function: subplotlib::steplibrary::files::mtime_is_recent - -- then: file {filename} has a very old modification time - function: subplotlib::steplibrary::files::mtime_is_ancient - -- given: a directory {path} - function: subplotlib::steplibrary::files::make_directory - -- when: I create directory {path} - function: subplotlib::steplibrary::files::make_directory - -- when: I remove directory {path} - function: subplotlib::steplibrary::files::remove_directory - -- then: directory {path} exists - function: subplotlib::steplibrary::files::path_exists - -- then: directory {path} does not exist - function: subplotlib::steplibrary::files::path_does_not_exist - -- then: directory {path} is empty - function: subplotlib::steplibrary::files::path_is_empty - -- then: directory {path} is not empty - function: subplotlib::steplibrary::files::path_is_not_empty diff --git a/share/rust/lib/runcmd.yaml b/share/rust/lib/runcmd.yaml deleted file mode 100644 index fc2db6b..0000000 --- a/share/rust/lib/runcmd.yaml +++ /dev/null @@ -1,97 +0,0 @@ -# Bindings for the runcmd steplibrary - -- given: helper script {script} for runcmd - function: subplotlib::steplibrary::runcmd::helper_script - types: - script: file - -- given: srcdir is in the PATH - function: subplotlib::steplibrary::runcmd::helper_srcdir_path - -- when: I run (?P<argv0>\S+)(?P<args>.*) - regex: true - function: subplotlib::steplibrary::runcmd::run - -- when: I run, in (?P<dirname>\S+), (?P<argv0>\S+)(?P<args>.*) - regex: true - function: subplotlib::steplibrary::runcmd::run_in - -- when: I try to run (?P<argv0>\S+)(?P<args>.*) - regex: true - function: subplotlib::steplibrary::runcmd::try_to_run - -- when: I try to run, in (?P<dirname>\S+), (?P<argv0>\S+)(?P<args>.*) - regex: true - function: subplotlib::steplibrary::runcmd::try_to_run_in - -# Steps to examine exit code of latest command. - -- then: exit code is {exit} - function: subplotlib::steplibrary::runcmd::exit_code_is - types: - exit: int - -- then: exit code is not {exit} - function: subplotlib::steplibrary::runcmd::exit_code_is_not - types: - exit: int - -- then: command is successful - function: subplotlib::steplibrary::runcmd::exit_code_is_zero - -- then: command fails - function: subplotlib::steplibrary::runcmd::exit_code_is_nonzero - -# Steps to examine stdout/stderr for exact content. - -- then: stdout is exactly "(?P<text>.*)" - regex: true - function: subplotlib::steplibrary::runcmd::stdout_is - -- then: 'stdout isn''t exactly "(?P<text>.*)"' - regex: true - function: subplotlib::steplibrary::runcmd::stdout_isnt - -- then: stderr is exactly "(?P<text>.*)" - regex: true - function: subplotlib::steplibrary::runcmd::stderr_is - -- then: 'stderr isn''t exactly "(?P<text>.*)"' - regex: true - function: subplotlib::steplibrary::runcmd::stderr_isnt - -# Steps to examine stdout/stderr for sub-strings. - -- then: stdout contains "(?P<text>.*)" - regex: true - function: subplotlib::steplibrary::runcmd::stdout_contains - -- then: 'stdout doesn''t contain "(?P<text>.*)"' - regex: true - function: subplotlib::steplibrary::runcmd::stdout_doesnt_contain - -- then: stderr contains "(?P<text>.*)" - regex: true - function: subplotlib::steplibrary::runcmd::stderr_contains - -- then: 'stderr doesn''t contain "(?P<text>.*)"' - regex: true - function: subplotlib::steplibrary::runcmd::stderr_doesnt_contain - -# Steps to match stdout/stderr against regular expressions. - -- then: stdout matches regex (?P<regex>.*) - regex: true - function: subplotlib::steplibrary::runcmd::stdout_matches_regex - -- then: stdout doesn't match regex (?P<regex>.*) - regex: true - function: subplotlib::steplibrary::runcmd::stdout_doesnt_match_regex - -- then: stderr matches regex (?P<regex>.*) - regex: true - function: subplotlib::steplibrary::runcmd::stderr_matches_regex - -- then: stderr doesn't match regex (?P<regex>.*) - regex: true - function: subplotlib::steplibrary::runcmd::stderr_doesnt_match_regex |