diff --git a/README.md b/README.md index 259c308..2d8c3f8 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,29 @@ If you use Nix with flakes, you can simply add it to your program's devshell ins ) ``` +## What do you put in your commit messages? + +Simply add a bash script enclosed in a `git-check-assertions` block to a commit message, e.g.: + +~~~ +```git-check-assertions +dotnet build +dotnet test --no-build +``` +~~~ + +This script will be run with `set -euo pipefail`, and the commit will be considered correct if the script exits succesfully. + +You can technically assert that a command fails by writing `! ... || exit 1`, or write assertions about a command's output by piping it to `grep`, but doing so won't lead to very useful error messages when things go wrong. To make those things easier, there are some helper functions included, which are inspired by [bats](https://github.com/bats-core/bats-core) and [bats-assert](https://github.com/bats-core/bats-assert): + +* `run `: run a command, capturing its exit status in `status` and combined stdout/stderr in `output`. +* `assert_success`: succeed if `run` produced a zero exit status. +* `assert_failure`: succeed if `run` produced a non-zero exit status. +* `assert_output `: succeed if `output` exactly matches the string. +* `assert_output --partial `: succeed if `output` contains the string. + +I'm considering taking `bats-assert` as a dependency, but for now, this very minimal set of functions with a similar interface should get you on your way. + ## Examples of commit messages Assert that a commit builds: @@ -65,16 +88,18 @@ Assert that a commit builds and that the tests succeed: ~~~ ```git-check-assertions dotnet build -dotnet test --no-build +run dotnet test --no-build +assert_success ``` ~~~ -Assert that a commit builds, but that the tests do not succeed (`assert_fails` is a helper function included in `git-check-assertions`): +Assert that a commit builds, but that the tests do not succeed: ~~~ ```git-check-assertions dotnet build -assert_fails dotnet test --no-build +run dotnet test --no-build +assert_failure ``` ~~~ @@ -83,7 +108,9 @@ Assert that a commit builds, and that the tests fail with exactly the error that ~~~ ```git-check-assertions dotnet build -(assert_fails dotnet test --no-build) | grep "Invalid URL" +run dotnet test --no-build +assert_failure +assert_output --partial "Invalid URL" ``` ~~~ @@ -94,6 +121,7 @@ Assert that a commit builds, and that a specific change breaks the tests (as dis dotnet test sed -i '/crucial code/d' Main.fs dotnet build -assert_fails dotnet test --no-build +run dotnet test --no-build +assert_failure ``` ~~~ diff --git a/bin/git-check-assertions b/bin/git-check-assertions index f7fe936..5501358 100755 --- a/bin/git-check-assertions +++ b/bin/git-check-assertions @@ -24,14 +24,45 @@ set -euo pipefail -assert_fails() { - ! "$@" || { +# helper functions inspired by bats/bats-assert +run() { + set +e + output="$("$@" 2>&1)" + status=$? + set -e + return 0 +} +assert_success() { + if [ "$status" -ne 0 ]; then + echo "Expected command to succeed, but it failed." + exit 1 + fi +} +assert_failure() { + if [ "$status" -eq 0 ]; then echo "Expected command to fail, but it succeeded." exit 1 - } + fi } -export -f assert_fails +assert_output() { + if [ "${1:-}" = "--partial" ]; then + local expected="$2" + if [[ "$output" != *"$expected"* ]]; then + echo "Expected output to contain: $expected" + echo "Actual output: $output" + exit 1 + fi + return 0 + fi + if [ "$output" != "$1" ]; then + echo "Expected output to equal: $1" + echo "Actual output: $output" + exit 1 + fi +} +export -f run assert_success assert_failure assert_output +# main flow orig_ref="$(git symbolic-ref --quiet --short HEAD || git rev-parse HEAD)" base="$(git merge-base main HEAD)" mapfile -t commits < <(git rev-list --reverse "${base}..HEAD") diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats index f58c3ea..697d47d 100755 --- a/test/git-check-assertions.bats +++ b/test/git-check-assertions.bats @@ -112,3 +112,90 @@ commit_with_assertion() { assert_success } + +@test "assert_success should succeed if the executed command succeeded" { + git checkout -b feature + commit_with_assertion $'run exit 0\nassert_success' + + run git-check-assertions + + assert_success +} + +@test "assert_success should fail if the executed command failed" { + git checkout -b feature + commit_with_assertion $'run exit 1\nassert_success' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected command to succeed, but it failed." +} + +@test "assert_failure should succeed if the executed command failed" { + git checkout -b feature + commit_with_assertion $'run exit 1\nassert_failure' + + run git-check-assertions + + assert_success +} + +@test "assert_failure should fail if the executed command succeeded" { + git checkout -b feature + commit_with_assertion $'run exit 0\nassert_failure' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected command to fail, but it succeeded." +} + +@test "assert_output should succeed if the output matches the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output hello' + + run git-check-assertions + + assert_success +} + +@test "assert_output should fail if the output does not match the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output goodbye' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected output to equal: goodbye" + assert_output --partial "Actual output: hello" +} + +@test "assert_output should also check stderr output" { + git checkout -b feature + commit_with_assertion $'run sh -c "echo err 1>&2"\nassert_output err' + + run git-check-assertions + + assert_success +} + +@test "assert_output --partial should succeed if output contains the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output --partial ell' + + run git-check-assertions + + assert_success +} + +@test "assert_output --partial should fail if output does not contain the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output --partial xyz' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected output to contain: xyz" + assert_output --partial "Actual output: hello" +}