From 7d152dad05872282595bf80e28bd0719be0fd8c2 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 28 Mar 2021 12:44:29 +0300 Subject: chore: add dependencies to Cargo.toml Also, drop Cargo.lock from .gitignore, as we don't want someone to randomly get different version when they build from source. We'll be updating the lock file for every release. --- .gitignore | 5 ++- Cargo.lock | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 96ef6c0..9f25980 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /target -Cargo.lock +*.pdf +*.html +test.log +test.py diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1b89008 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,149 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "anyhow" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "git" +version = "0.1.0" +dependencies = [ + "anyhow", + "pandoc", + "pandoc_ast", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "pandoc" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84060e442eb742cdba4038302c9924aecd67434073b9785dd9a6cec8a3f4065a" +dependencies = [ + "itertools", +] + +[[package]] +name = "pandoc_ast" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a940e63c65b34a7be2f847de6847b4bc9b68d74e3f7a5c648ca2fa5317f3bd06" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" + +[[package]] +name = "serde_derive" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/Cargo.toml b/Cargo.toml index 91c0a39..098f345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1" +pandoc = "0.8.4" +pandoc_ast = "0.8.0" +serde = "1.0.124" +serde_json = "1.0.64" +thiserror = "1" -- cgit v1.2.1 From b3167754c1e5a4ea211649f59199fe053b4aa031 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 28 Mar 2021 12:45:53 +0300 Subject: test: add script to build and run build-directory tests --- check | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 check diff --git a/check b/check new file mode 100755 index 0000000..3c68bdc --- /dev/null +++ b/check @@ -0,0 +1,35 @@ +#!/bin/sh +# +# Run the automated tests for the project. + +set -eu + +hideok=chronic +if [ "$#" -gt 0 ] +then + case "$1" in + verbose | -v | --verbose) + hideok= + shift + ;; + esac +fi + +got_cargo_cmd() +{ + cargo --list | grep " $1 " > /dev/null +} + +got_cargo_cmd clippy && cargo clippy -q +$hideok cargo build --all-targets +got_cargo_cmd fmt && $hideok cargo fmt -- --check +$hideok cargo test + +sp-docgen bumper.md -o bumper.html +sp-docgen bumper.md -o bumper.pdf + +sp-codegen bumper.md -o test.py +rm -f test.log +$hideok python3 test.py --log test.log "$@" + +echo "Everything seems to be in order." -- cgit v1.2.1 From 721e8e165914f199cb803465d58ba74e0df47d49 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 28 Mar 2021 12:45:53 +0300 Subject: doc: add a placeholder for release notes --- NEWS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 NEWS diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..f7e3810 --- /dev/null +++ b/NEWS @@ -0,0 +1,3 @@ +# Release notes for Bumper + +## Version 1.0, not yet released -- cgit v1.2.1 From c3980edbea7638dfd88369a712495bee9a5f6384 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 28 Mar 2021 12:45:00 +0300 Subject: test: add vendored copies of Subplot libraries We'll need these soon. --- src/lib.rs | 8 +- subplot/vendored/files.py | 194 +++++++++++++++++++++++++++++++++ subplot/vendored/files.yaml | 83 ++++++++++++++ subplot/vendored/runcmd.py | 252 +++++++++++++++++++++++++++++++++++++++++++ subplot/vendored/runcmd.yaml | 83 ++++++++++++++ 5 files changed, 613 insertions(+), 7 deletions(-) create mode 100644 subplot/vendored/files.py create mode 100644 subplot/vendored/files.yaml create mode 100644 subplot/vendored/runcmd.py create mode 100644 subplot/vendored/runcmd.yaml diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..8b13789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} + diff --git a/subplot/vendored/files.py b/subplot/vendored/files.py new file mode 100644 index 0000000..dd5b9f8 --- /dev/null +++ b/subplot/vendored/files.py @@ -0,0 +1,194 @@ +import logging +import os +import re +import shutil +import time + + +def files_create_from_embedded(ctx, filename=None): + files_make_directory(ctx, path=os.path.dirname(filename) or ".") + 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"] + + 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)) + + +def files_create_from_text(ctx, filename=None, text=None): + files_make_directory(ctx, path=os.path.dirname(filename) or ".") + with open(filename, "w") as f: + f.write(text) + + +def files_make_directory(ctx, path=None): + path = "./" + path + if not os.path.exists(path): + os.makedirs(path) + + +def files_remove_directory(ctx, path=None): + path = "./" + path + shutil.rmtree(path) + + +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_directory_exists(ctx, path=None): + assert_eq = globals()["assert_eq"] + assert_eq(os.path.isdir(path), True) + + +def files_directory_does_not_exist(ctx, path=None): + assert_eq = globals()["assert_eq"] + assert_eq(os.path.isdir(path), False) + + +def files_directory_is_empty(ctx, path=None): + assert_eq = globals()["assert_eq"] + assert_eq(os.listdir(path), []) + + +def files_directory_is_not_empty(ctx, path=None): + assert_ne = globals()["assert_ne"] + assert_ne(os.listdir(path), 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 new file mode 100644 index 0000000..f18b8cd --- /dev/null +++ b/subplot/vendored/files.yaml @@ -0,0 +1,83 @@ +- 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.*)" to file (?P\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.+) exist + function: files_only_these_exist + regex: true + +- then: file (?P\S+) contains "(?P.*)" + regex: true + function: files_file_contains + +- then: file (?P\S+) matches regex /(?P.*)/ + regex: true + function: files_file_matches_regex + +- then: file (?P\S+) matches regex "(?P.*)" + 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/subplot/vendored/runcmd.py b/subplot/vendored/runcmd.py new file mode 100644 index 0000000..a2564c6 --- /dev/null +++ b/subplot/vendored/runcmd.py @@ -0,0 +1,252 @@ +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 new file mode 100644 index 0000000..48dde90 --- /dev/null +++ b/subplot/vendored/runcmd.yaml @@ -0,0 +1,83 @@ +# 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\S+)(?P.*) + regex: true + function: runcmd_step + +- when: I try to run (?P\S+)(?P.*) + 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.*)" + regex: true + function: runcmd_stdout_is + +- then: "stdout isn't exactly \"(?P.*)\"" + regex: true + function: runcmd_stdout_isnt + +- then: stderr is exactly "(?P.*)" + regex: true + function: runcmd_stderr_is + +- then: "stderr isn't exactly \"(?P.*)\"" + regex: true + function: runcmd_stderr_isnt + +# Steps to examine stdout/stderr for sub-strings. + +- then: stdout contains "(?P.*)" + regex: true + function: runcmd_stdout_contains + +- then: "stdout doesn't contain \"(?P.*)\"" + regex: true + function: runcmd_stdout_doesnt_contain + +- then: stderr contains "(?P.*)" + regex: true + function: runcmd_stderr_contains + +- then: "stderr doesn't contain \"(?P.*)\"" + regex: true + function: runcmd_stderr_doesnt_contain + +# Steps to match stdout/stderr against regular expressions. + +- then: stdout matches regex (?P.*) + regex: true + function: runcmd_stdout_matches_regex + +- then: stdout doesn't match regex (?P.*) + regex: true + function: runcmd_stdout_doesnt_match_regex + +- then: stderr matches regex (?P.*) + regex: true + function: runcmd_stderr_matches_regex + +- then: stderr doesn't match regex (?P.*) + regex: true + function: runcmd_stderr_doesnt_match_regex -- cgit v1.2.1 From e5d772910319dc4ac5942a07a62ee138e8322455 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 28 Mar 2021 12:46:24 +0300 Subject: test: add first bumper-specific bindings and step functions --- subplot/bumper.py | 9 +++++++++ subplot/bumper.yaml | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 subplot/bumper.py create mode 100644 subplot/bumper.yaml diff --git a/subplot/bumper.py b/subplot/bumper.py new file mode 100644 index 0000000..66d2564 --- /dev/null +++ b/subplot/bumper.py @@ -0,0 +1,9 @@ +import os + + +def install_bumper(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")) diff --git a/subplot/bumper.yaml b/subplot/bumper.yaml new file mode 100644 index 0000000..f74c28e --- /dev/null +++ b/subplot/bumper.yaml @@ -0,0 +1,2 @@ +- given: "an installed Bumper" + function: install_bumper -- cgit v1.2.1 From 6413490031d0c538f98d5fbca045dffe41664734 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 28 Mar 2021 12:45:53 +0300 Subject: doc: add start of a subplot This only has one scenario for now, but explains what the project is about. I expect this to eventually change entirely, but this will do as a start. --- bumper.md | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 bumper.md diff --git a/bumper.md b/bumper.md new file mode 100644 index 0000000..fcf4005 --- /dev/null +++ b/bumper.md @@ -0,0 +1,99 @@ +# Introduction + +Bumper is a small utility for updating the version number when making +a release of a software project. It updates the version number in the +various locations in which it is stored in the source tree, creates a +git tag for indicating the release, then updates the source tree again +with a version number that indicates an unreleased version. + +The tool is used like this: + +~~~{.numberLines} +$ cd ~/my-project +$ bumper 1.2.0 +Rust project my-project version set to 1.2.0 +Debian package version set to 1.2.0-1 +Release tagged as v1.2.0 +$ git push --tags +... +$ +~~~ + +In other words: you run Bumper and tell it the version of the release +you're making. Bumper sniffs out what kind of project it is, and sets +the version number in the right places. It then creates a git tag for +that release. It's up to you to push the changes and tag, or to build +the release: Bumper only makes local changes. + +# Overview + +Bumper makes several assumptions about the project and its source +tree, to keep things simple. + +* Version numbers for releases use a format of X.Y.Z, where each + component is an integer. For example, 12.7.999. There can be any + number of components. + +* The source is stored in git. No other version control systems are + supported, since the author uses nothing else. (If you would like + support for other systems, please help.) + +* Python projects store the version number in a file `version.py` in a + subdirectory. The project build system uses that file as the source + of the version number. + +* Rust projects store the version number in the `Cargo.toml` file. The + build system gets it from there. + +* Debian packages have a `debian/changelog`, which specifies the + package's version number. + +* Release notes are in a markdown file called `NEWS`. + +* Releases are marked with signed, annotated git tags named after the + release version with a `v` prefix. + +Bumper looks for all the files mentioned above, and updates or +overwrites them to set the version number, and then commits all +changes. It creates a release tag for the commit. It then updates the +same files to add a "`+git`" suffix to the version number, to help +distinguish release versions from development versions. + +# Acceptance criteria + +This chapter has scenarios to express acceptance criteria and their +verification. + +## Does nothing if there are no files to update + +This scenario verifies that Bumper doesn't change anything if it +doesn't find any files it knows about. + +~~~scenario +given an installed Bumper +given file random.txt +when I try to run bumper 1.2.3 +then command fails +then only files random.txt exist +~~~ + +~~~{#random.txt .file} +Some random text file Bumper knows nothing about. +~~~ + + + +--- +title: bumper – set version number for a project +author: Lars Wirzenius +documentclass: report +template: python +bindings: +- subplot/bumper.yaml +- subplot/vendored/files.yaml +- subplot/vendored/runcmd.yaml +functions: +- subplot/bumper.py +- subplot/vendored/files.py +- subplot/vendored/runcmd.py +... -- cgit v1.2.1 From af24be332de7c6762358c3c34b6dc266b7807154 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 28 Mar 2021 12:46:36 +0300 Subject: feat: implement dummy main program This doesn't do anything useful. It's mainly a placeholder, but it lets us run the subplot. --- src/bin/bumper.rs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/bin/bumper.rs diff --git a/src/bin/bumper.rs b/src/bin/bumper.rs new file mode 100644 index 0000000..081ca86 --- /dev/null +++ b/src/bin/bumper.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(1); +} -- cgit v1.2.1