summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-10-13 19:11:34 +0000
committerLars Wirzenius <liw@liw.fi>2021-10-13 19:11:34 +0000
commit951ea27cc9263c004c2ae171435affdc68ff7d9f (patch)
treec343641b2e01644302d349c64c417ac481aeeeed
parent59375983ef9a7f69d7a1e4250da5babee9c302c7 (diff)
parentdde8f03212e7605b693d75868733979f9bbc425d (diff)
downloadsubplot-951ea27cc9263c004c2ae171435affdc68ff7d9f.tar.gz
Merge branch 'nix-support' into 'main'
Support Nix-based and NixOS systems - plus fewer assumptions for Windows/MacOS See merge request subplot/subplot!220
-rw-r--r--.envrc1
-rw-r--r--DECISIONS.md12
-rwxr-xr-xcheck9
-rw-r--r--examples/echo/echo.md6
-rw-r--r--flake.lock40
-rw-r--r--flake.nix37
-rw-r--r--share/bash/template/template.sh.tera14
-rw-r--r--share/python/lib/daemon.py15
-rw-r--r--share/python/template/scenarios.py6
-rw-r--r--subplotlib/runcmd.md24
-rw-r--r--subplotlib/src/steplibrary/runcmd.rs10
-rw-r--r--subplotlib/tests/runcmd.rs40
-rw-r--r--tests/python/daemon.md18
-rw-r--r--tests/python/runcmd.md24
14 files changed, 179 insertions, 77 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/DECISIONS.md b/DECISIONS.md
index 00a41fb..fe67b1b 100644
--- a/DECISIONS.md
+++ b/DECISIONS.md
@@ -25,6 +25,18 @@ MSRV forward as and when our client projects move forward.
Who: Daniel, Lars
+## Do not clear/override all environment variables
+
+Date: 2021-10-08
+
+What: We decided that the environment being "clean" is more the
+resposibility of the caller of the test suite than of the test
+suite itself. As such, we have decided to only set SHELL, HOME,
+and TMPDIR. Importantly we do not override PATH etc. so that
+things will work on NixOS etc.
+
+Who: Daniel, Lars.
+
## Start decision log
Date: 2021-09-11
diff --git a/check b/check
index 601f900..b7c5164 100755
--- a/check
+++ b/check
@@ -90,6 +90,11 @@ class Runcmd:
p = self.runcmd_unchecked(["which", name], stdout=DEVNULL)
return p.returncode == 0
+ def pandoc_is_newer(self):
+ """Is pandoc new enough for --citeproc"""
+ p = self.runcmd(["pandoc", "--help"], stdout=PIPE)
+ return "--citeproc" in p.stdout.decode("UTF-8")
+
def cargo(self, args, **kwargs):
"""Run cargo with arguments."""
self.runcmd(["cargo"] + args, **kwargs)
@@ -329,6 +334,10 @@ def check_tooling(r):
]
for command in commands:
if not r.got_command(command):
+ if command == "pandoc-citeproc":
+ if r.pandoc_is_newer():
+ r.msg(" Fortunately pandoc is new enough for --citeproc, no need for pandoc-citeproc")
+ continue
sys.exit(f"can't find {command}, which is needed for test suite")
if not r.got_command("daemonize") and not r.got_command("/usr/sbin/daemonize"):
diff --git a/examples/echo/echo.md b/examples/echo/echo.md
index 8bf5405..f7a43b3 100644
--- a/examples/echo/echo.md
+++ b/examples/echo/echo.md
@@ -12,14 +12,14 @@ Introduction
**echo**(1) is a Unix command line tool, which writes its command line
arguments to the standard output. This is a simple acceptance test
-suite for the `/bin/echo` implementation.
+suite for the `echo` implementation.
For more information, see [@foo2020].
No arguments
=============================================================================
-Run `/bin/echo` without arguments.
+Run `echo` without arguments.
```scenario
when user runs echo without arguments
@@ -31,7 +31,7 @@ then standard error is empty
Hello, world
=============================================================================
-This scenario runs `/bin/echo` to produce the output "hello, world".
+This scenario runs `echo` to produce the output "hello, world".
```scenario
when user runs echo with arguments hello, world
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..38de242
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,40 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "locked": {
+ "lastModified": 1631561581,
+ "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1632639184,
+ "narHash": "sha256-fRLxre+gPxIkjFVj17O68pyAWU1cxT20XFOiulIWzRw=",
+ "path": "/nix/store/mz8vpyg587llb4c802w96m956icc39vm-source",
+ "rev": "fd8a7fd07da0f3fc0e27575891f45c2f88e5dd44",
+ "type": "path"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..c19f3bc
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,37 @@
+# This is a nix "flake" which is used both by `direnv` and to offer
+# a quick way to acquire `subplot` in a nix/nixos environment.
+# If you have `direnv` support, just allow this tree and you should
+# be able to develop `subplot`. If you use this as a package flake
+# then the `subplot` package derivation is available in the usual way
+
+{
+ inputs = { flake-utils.url = "github:numtide/flake-utils"; };
+
+ outputs = { self, nixpkgs, flake-utils }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ test-python-packages = python-packages:
+ with python-packages;
+ [ requests ];
+
+ in {
+ devShell = pkgs.mkShell {
+ buildInputs = with pkgs; [
+ stdenv
+ graphviz
+ plantuml
+ pandoc
+ texlive.combined.scheme-medium
+ daemonize
+ librsvg
+ (python3.withPackages test-python-packages)
+ ];
+ shellHook = ''
+ export SUBPLOT_DOT_PATH=${pkgs.graphviz}/bin/dot
+ export SUBPLOT_JAVA_PATH=${pkgs.jre}/bin/java
+ export SUBPLOT_PLANTUML_JAR_PATH=${pkgs.plantuml}/lib/plantuml.jar
+ '';
+ };
+ });
+}
diff --git a/share/bash/template/template.sh.tera b/share/bash/template/template.sh.tera
index da748be..12d72ea 100644
--- a/share/bash/template/template.sh.tera
+++ b/share/bash/template/template.sh.tera
@@ -163,20 +163,8 @@ scenario_{{ loop.index }}() {
{% endfor %}
#############################################################################
-# Make the environment minimal.
-
-# Write to stdout the names of all environment variables, one per
-# line. Handle also cases where the value of an environment variable
-# contains newlines.
-envnames()
-{
- env -0 | sed -z 's/^\([^=]\+\)=.*$/\1/' | tr '\0' '\n'
-}
+# Update the environment
-# Unset all environment variables. At the beginning of each scenario,
-# some additional ones will be set to the per-scenario directory.
-unset $(envnames)
-export PATH=/bin:/usr/bin
export SHELL=/bin/sh
# Include any configured environment variables
diff --git a/share/python/lib/daemon.py b/share/python/lib/daemon.py
index 9f1dfc0..1d0d443 100644
--- a/share/python/lib/daemon.py
+++ b/share/python/lib/daemon.py
@@ -25,7 +25,7 @@ def daemon_start_on_port(ctx, path=None, args=None, name=None, port=None, env=No
# Start a daemon after a little wait. This is used only for testing the
# port-waiting code.
def _daemon_start_soonish(ctx, path=None, args=None, name=None, port=None, env=None):
- _daemon_start(ctx, path=os.path.abspath(path), args=args, name=name, env=env)
+ _daemon_start(ctx, path=path, args=args, name=name, env=env)
daemon = ctx.declare("_daemon")
# Store the PID of the process we just started so that _daemon_stop_soonish
@@ -53,6 +53,18 @@ def _daemon_stop_soonish(ctx, path=None, args=None, name=None, port=None, env=No
logging.warning("Process did not actually exist (anymore?)")
+# Find a binary akin to the `which` or `whereis` command
+def _daemon_whereis(path):
+ if "/" in path:
+ logging.debug(f"Not using PATH for daemon {path}")
+ return path
+ for prefix in os.environ["PATH"].split(":"):
+ absolute = os.path.join(prefix, path)
+ logging.debug(f"Checking for {absolute}")
+ if os.access(absolute, os.X_OK) and os.path.isfile(absolute):
+ return absolute
+ return path
+
# 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.
@@ -63,6 +75,7 @@ def _daemon_start(ctx, path=None, args=None, name=None, env=None):
runcmd_get_stderr = globals()["runcmd_get_stderr"]
runcmd_prepend_to_path = globals()["runcmd_prepend_to_path"]
+ path = _daemon_whereis(path)
path = os.path.abspath(path)
argv = [path] + args.split()
diff --git a/share/python/template/scenarios.py b/share/python/template/scenarios.py
index 96dcc69..b215133 100644
--- a/share/python/template/scenarios.py
+++ b/share/python/template/scenarios.py
@@ -83,15 +83,13 @@ class Scenario:
def _set_environment_variables_to(self, scendir, extra_env):
log_value = globals()["log_value"]
- minimal = {
- "PATH": "/bin:/usr/bin",
+ overrides = {
"SHELL": "/bin/sh",
"HOME": scendir,
"TMPDIR": scendir,
}
- os.environ.clear()
- os.environ.update(minimal)
+ os.environ.update(overrides)
os.environ.update(extra_env)
if not self._logged_env:
self._logged_env = True
diff --git a/subplotlib/runcmd.md b/subplotlib/runcmd.md
index d6210fb..50e41f6 100644
--- a/subplotlib/runcmd.md
+++ b/subplotlib/runcmd.md
@@ -22,8 +22,8 @@ they're verified. It uses the steps and functions from the
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
+The scenarios use the Unix commands `true` and `false` to
+generate exit codes, and `echo` to produce stdout. To generate
stderr, they use the little helper script below.
```{#err.sh .file .sh .numberLines}
@@ -40,7 +40,7 @@ variations.
## Successful execution
```scenario
-when I run /bin/true
+when I run true
then exit code is 0
and command is successful
```
@@ -49,7 +49,7 @@ and command is successful
```scenario
given a directory xyzzy
-when I run, in xyzzy, /bin/pwd
+when I run, in xyzzy, pwd
then exit code is 0
then command is successful
then stdout contains "/xyzzy"
@@ -58,7 +58,7 @@ then stdout contains "/xyzzy"
## Failed execution
```scenario
-when I try to run /bin/false
+when I try to run false
then exit code is not 0
and command fails
```
@@ -67,7 +67,7 @@ and command fails
```scenario
given a directory xyzzy
-when I try to run, in xyzzy, /bin/false
+when I try to run, in xyzzy, false
then exit code is not 0
and command fails
```
@@ -84,7 +84,7 @@ to the reader what's inside. Also, C-style string escapes are
understood.
```scenario
-when I run /bin/echo hello, world
+when I run echo hello, world
then stdout is exactly "hello, world\n"
```
@@ -102,7 +102,7 @@ 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
+when I run echo hello, world
then stdout contains "world\n"
and exit code is 0
```
@@ -122,7 +122,7 @@ 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
+when I run echo hello, world
then stdout matches regex world$
```
@@ -142,7 +142,7 @@ have something we want to avoid.
## Check stdout is not exactly something
```scenario
-when I run /bin/echo hi
+when I run echo hi
then stdout isn't exactly "hello, world\n"
```
@@ -157,7 +157,7 @@ then stderr isn't exactly "hello, world\n"
## Check stdout doesn't contain sub-string
```scenario
-when I run /bin/echo hi
+when I run echo hi
then stdout doesn't contain "world"
```
@@ -172,7 +172,7 @@ then stderr doesn't contain "world"
## Check stdout doesn't match regular expression
```scenario
-when I run /bin/echo hi
+when I run echo hi
then stdout doesn't match regex world$
```
diff --git a/subplotlib/src/steplibrary/runcmd.rs b/subplotlib/src/steplibrary/runcmd.rs
index 7075b68..2bf8c56 100644
--- a/subplotlib/src/steplibrary/runcmd.rs
+++ b/subplotlib/src/steplibrary/runcmd.rs
@@ -46,8 +46,13 @@ impl ContextElement for Runcmd {
self.env.drain();
self.paths.drain(..);
self.env.insert("SHELL".into(), "/bin/sh".into());
- self.env
- .insert("PATH".into(), env::join_paths(DEFAULT_PATHS.iter())?);
+ self.env.insert(
+ "PATH".into(),
+ env::var_os("PATH")
+ .map(Ok)
+ .unwrap_or_else(|| env::join_paths(DEFAULT_PATHS.iter()))?,
+ );
+
// Having assembled the 'default' environment, override it with injected
// content from the calling environment.
for (k, v) in env::vars_os() {
@@ -125,7 +130,6 @@ pub fn try_to_run_in(context: &ScenarioContext, dirname: &str, argv0: &str, args
let mut proc = Command::new(argv0);
proc.args(&shell_words::split(args)?);
proc.current_dir(&datadir);
- proc.env_clear();
proc.env("HOME", &datadir);
proc.env("TMPDIR", &datadir);
diff --git a/subplotlib/tests/runcmd.rs b/subplotlib/tests/runcmd.rs
index f18f463..0c677ba 100644
--- a/subplotlib/tests/runcmd.rs
+++ b/subplotlib/tests/runcmd.rs
@@ -18,8 +18,8 @@ fn successful_execution() {
let step = subplotlib::steplibrary::runcmd::run::Builder::default()
.argv0(
- // "/bin/true"
- &base64_decode("L2Jpbi90cnVl"),
+ // "true"
+ &base64_decode("dHJ1ZQ=="),
)
.args(
// ""
@@ -62,8 +62,8 @@ fn successful_execution_in_a_sub_directory() {
&base64_decode("eHl6enk="),
)
.argv0(
- // "/bin/pwd"
- &base64_decode("L2Jpbi9wd2Q="),
+ // "pwd"
+ &base64_decode("cHdk"),
)
.args(
// ""
@@ -100,8 +100,8 @@ fn failed_execution() {
let step = subplotlib::steplibrary::runcmd::try_to_run::Builder::default()
.argv0(
- // "/bin/false"
- &base64_decode("L2Jpbi9mYWxzZQ=="),
+ // "false"
+ &base64_decode("ZmFsc2U="),
)
.args(
// ""
@@ -144,8 +144,8 @@ fn failed_execution_in_a_sub_directory() {
&base64_decode("eHl6enk="),
)
.argv0(
- // "/bin/false"
- &base64_decode("L2Jpbi9mYWxzZQ=="),
+ // "false"
+ &base64_decode("ZmFsc2U="),
)
.args(
// ""
@@ -176,8 +176,8 @@ fn check_stdout_is_exactly_as_wanted() {
let step = subplotlib::steplibrary::runcmd::run::Builder::default()
.argv0(
- // "/bin/echo"
- &base64_decode("L2Jpbi9lY2hv"),
+ // "echo"
+ &base64_decode("ZWNobw=="),
)
.args(
// " hello, world"
@@ -254,8 +254,8 @@ fn check_stdout_using_sub_string_search() {
let step = subplotlib::steplibrary::runcmd::run::Builder::default()
.argv0(
- // "/bin/echo"
- &base64_decode("L2Jpbi9lY2hv"),
+ // "echo"
+ &base64_decode("ZWNobw=="),
)
.args(
// " hello, world"
@@ -337,8 +337,8 @@ fn check_stdout_using_regular_expressions() {
let step = subplotlib::steplibrary::runcmd::run::Builder::default()
.argv0(
- // "/bin/echo"
- &base64_decode("L2Jpbi9lY2hv"),
+ // "echo"
+ &base64_decode("ZWNobw=="),
)
.args(
// " hello, world"
@@ -415,8 +415,8 @@ fn check_stdout_is_not_exactly_something() {
let step = subplotlib::steplibrary::runcmd::run::Builder::default()
.argv0(
- // "/bin/echo"
- &base64_decode("L2Jpbi9lY2hv"),
+ // "echo"
+ &base64_decode("ZWNobw=="),
)
.args(
// " hi"
@@ -493,8 +493,8 @@ fn check_stdout_doesn_t_contain_sub_string() {
let step = subplotlib::steplibrary::runcmd::run::Builder::default()
.argv0(
- // "/bin/echo"
- &base64_decode("L2Jpbi9lY2hv"),
+ // "echo"
+ &base64_decode("ZWNobw=="),
)
.args(
// " hi"
@@ -571,8 +571,8 @@ fn check_stdout_doesn_t_match_regular_expression() {
let step = subplotlib::steplibrary::runcmd::run::Builder::default()
.argv0(
- // "/bin/echo"
- &base64_decode("L2Jpbi9lY2hv"),
+ // "echo"
+ &base64_decode("ZWNobw=="),
)
.args(
// " hi"
diff --git a/tests/python/daemon.md b/tests/python/daemon.md
index 285f9f8..6d9b8f4 100644
--- a/tests/python/daemon.md
+++ b/tests/python/daemon.md
@@ -17,11 +17,11 @@ 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
+given there is no "sleep 12765" process
+when I start "sleep 12765" as a background process as sleepyhead
+then a process "sleep 12765" is running
when I stop background process sleepyhead
-then there is no "/bin/sleep 12765" process
+then there is no "sleep 12765" process
~~~
@@ -68,12 +68,12 @@ 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
+given there is no "sleep 12765" process
+when I try to start "sleep 12765" as sleepyhead, on port 8888
then starting daemon fails with "ConnectionRefusedError"
-then a process "/bin/sleep 12765" is running
+then a process "sleep 12765" is running
when I stop background process sleepyhead
-then there is no "/bin/sleep 12765" process
+then there is no "sleep 12765" process
~~~
@@ -101,7 +101,7 @@ have had time to produce it yet.
~~~{#chatty-daemon.sh .file .sh .numberLines}
-#!/bin/bash
+#!/usr/bin/env bash
set -euo pipefail
diff --git a/tests/python/runcmd.md b/tests/python/runcmd.md
index 68465a8..7b88dd3 100644
--- a/tests/python/runcmd.md
+++ b/tests/python/runcmd.md
@@ -14,8 +14,8 @@ they're verified. It uses the steps and functions from the
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
+The scenarios use the Unix commands `true` and `false` to
+generate exit codes, and `echo` to produce stdout. To generate
stderr, they use the little helper script below.
~~~{#err.sh .file .sh .numberLines}
@@ -32,7 +32,7 @@ variations.
## Successful execution
~~~scenario
-when I run /bin/true
+when I run true
then exit code is 0
and command is successful
~~~
@@ -41,7 +41,7 @@ and command is successful
~~~scenario
given a directory xyzzy
-when I run, in xyzzy, /bin/pwd
+when I run, in xyzzy, pwd
then exit code is 0
then command is successful
then stdout contains "/xyzzy"
@@ -50,7 +50,7 @@ then stdout contains "/xyzzy"
## Failed execution
~~~scenario
-when I try to run /bin/false
+when I try to run false
then exit code is not 0
and command fails
~~~
@@ -59,7 +59,7 @@ and command fails
~~~scenario
given a directory xyzzy
-when I try to run, in xyzzy, /bin/false
+when I try to run, in xyzzy, false
then exit code is not 0
and command fails
~~~
@@ -97,7 +97,7 @@ to the reader what's inside. Also, C-style string escapes are
understood.
~~~scenario
-when I run /bin/echo hello, world
+when I run echo hello, world
then stdout is exactly "hello, world\n"
~~~
@@ -115,7 +115,7 @@ 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
+when I run echo hello, world
then stdout contains "world\n"
and exit code is 0
~~~
@@ -135,7 +135,7 @@ 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
+when I run echo hello, world
then stdout matches regex world$
~~~
@@ -155,7 +155,7 @@ have something we want to avoid.
## Check stdout is not exactly something
~~~scenario
-when I run /bin/echo hi
+when I run echo hi
then stdout isn't exactly "hello, world\n"
~~~
@@ -170,7 +170,7 @@ then stderr isn't exactly "hello, world\n"
## Check stdout doesn't contain sub-string
~~~scenario
-when I run /bin/echo hi
+when I run echo hi
then stdout doesn't contain "world"
~~~
@@ -185,7 +185,7 @@ then stderr doesn't contain "world"
## Check stdout doesn't match regular expression
~~~scenario
-when I run /bin/echo hi
+when I run echo hi
then stdout doesn't match regex world$
~~~