From d20d0987debe37a30d416c8bf27634123165b5dd Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 30 Aug 2019 02:45:10 -0400 Subject: [PATCH] prelude.mk: Use BATS to write bunch of tests In doing so, find and fix a bug in quote.shell that it trimmed trailing newlines. The fix involves exporting NL. --- prelude.mk | 6 +- prelude_str.mk | 1 + tests/common.bash | 72 +++++++++++++++++ tests/prelude.bats | 171 ++++++++++++++++++++++++++++++++++++++++ tests/prelude_go.bats | 15 ++++ tests/prelude_path.bats | 11 +++ tests/prelude_str.bats | 13 +++ 7 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 tests/common.bash create mode 100644 tests/prelude.bats create mode 100644 tests/prelude_go.bats create mode 100644 tests/prelude_path.bats create mode 100644 tests/prelude_str.bats diff --git a/prelude.mk b/prelude.mk index 94aaf2e0e6..42f3dff759 100644 --- a/prelude.mk +++ b/prelude.mk @@ -9,8 +9,8 @@ ## Outputs ## # # String support: -# - Variable: NL -# - Variable: SPACE +# - Variable: export NL +# - Variable: SPACE # # Path support: # - Function: path.trimprefix @@ -70,7 +70,7 @@ joinlist=$(if $(word 2,$2),$(firstword $2)$1$(call joinlist,$1,$(wordlist 2,$(wo # Based on # https://git.lukeshu.com/autothing/tree/build-aux/Makefile.once.head/00-quote.mk?id=9384e763b00774603208b3d44977ed0e6762a09a # but modified to make newlines work with shells other than Bash. -quote.shell = "$$(printf '%s\n' $(subst $(NL),' ','$(subst ','\'',$1)'))" +quote.shell = $(subst $(NL),'"$${NL}"','$(subst ','\'',$1)') # Usage: VAR = $(call lazyonce,VAR,EXPR) # diff --git a/prelude_str.mk b/prelude_str.mk index 8a84c1566a..9ac84b0e86 100644 --- a/prelude_str.mk +++ b/prelude_str.mk @@ -9,6 +9,7 @@ define NL endef +export NL # NOTE: this is not a typo, this is actually how you spell space in Make define SPACE diff --git a/tests/common.bash b/tests/common.bash new file mode 100644 index 0000000000..a20c9dc860 --- /dev/null +++ b/tests/common.bash @@ -0,0 +1,72 @@ +#!/hint/bash + +common_setup() { + test_tmpdir="$(mktemp -d)" + ln -s "$BATS_TEST_DIRNAME/.." "$test_tmpdir/build-aux" + cd "$test_tmpdir" + cat >Makefile <<-'__EOT__' + .DEFAULT_GOAL = all + all: + .PHONY: all + include build-aux/prelude.mk + expr-eq-strict-actual: FORCE; printf '%s' $(call quote.shell,$(EXPR)) > $@ + expr-eq-echo-actual: FORCE; echo $(EXPR) > $@ + expr-eq-sloppy-actual: FORCE; echo $(foreach w,$(EXPR),$w) > $@ + __EOT__ +} +setup() { common_setup; } + +common_teardown() { + cd / + rm -rf -- "$test_tmpdir" +} +teardown() { common_teardown; } + +# Usage: check_executable SNIPPET.mk VARNAME +check_executable() { + [[ $# = 2 ]] + local snippet=$1 + local varname=$2 + + cat >>Makefile <<-__EOT__ + include build-aux/${snippet} + include build-aux/var.mk + all: \$(${varname}) \$(var.)${varname} + __EOT__ + + make + + local varvalue + varvalue="$(cat "build-aux/.var.${varname}")" + + [[ "$varvalue" == /* ]] + [[ -f "$varvalue" && -x "$varvalue" ]] + + eval "${varname}=\$varvalue" +} + +check_expr_eq() { + [[ $# = 3 ]] + local mode=$1 + local expr=$2 + local expected=$3 + + case "$mode" in + strict) printf '%s' "$expected" > expected;; + echo) echo $expected > expected;; + sloppy) echo $expected > expected;; + esac + + make EXPR="$expr" "expr-eq-${mode}-actual" + + diff -u expected "expr-eq-${mode}-actual" +} + +not() { + # This isn't just "I find 'not' more readable than '!'", it + # serves an actual purpose. '!' won't trigger an errexit, so + # it's no good for assertions. However, it can affect the + # return value of a function, and that function can trigger an + # errexit. + ! "$@" +} diff --git a/tests/prelude.bats b/tests/prelude.bats new file mode 100644 index 0000000000..4add6bbc17 --- /dev/null +++ b/tests/prelude.bats @@ -0,0 +1,171 @@ +#!/usr/bin/env bats + +load common + +@test "prelude.mk: joinlist with separator" { + check_expr_eq echo '$(call joinlist,/,foo bar baz)' 'foo/bar/baz' +} + +@test "prelude.mk: joinlist without separator" { + check_expr_eq echo '$(call joinlist,,foo bar baz)' 'foobarbaz' +} + +@test "prelude.mk: quote.shell" { + # This test relies on the fact that 'var.mk' is implemented + # using `quote.shell`. + cat >>Makefile <<-'__EOT__' + include build-aux/prelude.mk + include build-aux/var.mk + define actual + some'string"with`special characters) + and newlines and tabs + and 2 trailing newlines + + + endef + all: $(var.)actual + __EOT__ + + make + printf 'some'\''string"with`special characters)\nand newlines\tand tabs\nand 2 trailing newlines\n\n' > expected + diff -u expected build-aux/.var.actual +} + +@test "prelude.mk: lazyonce" { + cat >>Makefile <<-'__EOT__' + include build-aux/prelude.mk + var = $(call lazyonce,var,$(info eval-time)value) + $(info before) + $(info a: $(var)) + $(info b: $(var)) + $(info c: $(var)) + all: noop + noop: ; @true + .PHONY: noop + __EOT__ + + make > actual + printf '%s\n' > expected \ + 'before' \ + 'eval-time' \ + 'a: value' \ + 'b: value' \ + 'c: value' + diff -u expected actual +} + +@test "prelude.mk: build-aux.dir" { + cat >>Makefile <<-'__EOT__' + include build-aux/prelude.mk + include build-aux/var.mk + all: $(var.)build-aux.dir + __EOT__ + + make + # Check that it points to the right place + [[ "$(cat build-aux/.var.build-aux.dir)" -ef build-aux ]] +} + +@test "prelude.mk: build-aux.bindir" { + cat >>Makefile <<-'__EOT__' + include build-aux/prelude.mk + include build-aux/var.mk + all: $(build-aux.bindir) $(var.)build-aux.bindir + __EOT__ + + make + # Check that it points to the right place + [[ "$(cat build-aux/.var.build-aux.bindir)" -ef build-aux/bin ]] + # Check that it's absolute + [[ "$(cat build-aux/.var.build-aux.bindir)" == /* ]] +} + +@test "prelude.mk: FLOCK" { + if ! type flock &>/dev/null && ! type go &>/dev/null; then + skip + fi + check_executable prelude.mk FLOCK + if which flock &>/dev/null; then + [[ "$FLOCK" == "$(which flock)" ]] + fi + + # TODO: Check that $FLOCK behaves correctly +} + +@test "prelude.mk: COPY_IFCHANGED" { + check_executable prelude.mk COPY_IFCHANGED + + # TODO: Check that $COPY_IFCHANGED behaves correctly +} + +@test "prelude.mk: MOVE_IFCHANGED" { + check_executable prelude.mk MOVE_IFCHANGED + + # TODO: Check that $MOVE_IFCHANGED behaves correctly +} + +@test "prelude.mk: WRITE_IFCHANGED" { + check_executable prelude.mk WRITE_IFCHANGED + + # TODO: Check that $WRITE_IFCHANGED behaves correctly +} + +@test "prelude.mk: TAP_DRIVER" { + check_executable prelude.mk TAP_DRIVER + + # TODO: Check that $TAP_DRIVER behaves correctly +} + +@test "prelude.mk: clobber" { + if ! [[ -e build-aux/.git ]]; then + # Because we check `git clean -ndx` to make sure + # things are clean. + skip + fi + # Let's not work with a symlink for this test + rm "$test_tmpdir/build-aux" + cp -a "$BATS_TEST_DIRNAME/.." "$test_tmpdir/build-aux" + (cd build-aux && git clean -fdx) + + cat >>Makefile <<-'__EOT__' + include build-aux/prelude.mk + include build-aux/var.mk + all: $(COPY_IFCHANGED) $(MOVE_IFCHANGED) $(WRITE_IFCHANGED) $(TAP_DRIVER) + __EOT__ + + [[ -d build-aux ]] + [[ ! -d build-aux/bin ]] + make all + [[ -d build-aux/bin ]] + [[ -f build-aux/bin/copy-ifchanged && -x build-aux/bin/copy-ifchanged ]] + [[ -n "$(cd build-aux && git clean -ndx)" ]] + make clobber + [[ -d build-aux ]] + [[ ! -d build-aux/bin ]] + [[ -z "$(cd build-aux && git clean -ndx)" ]] +} + +@test "prelude.mk: build-aux.bin-go.rule" { + # TODO +} + +@test "prelude.mk: FORCE" { + cat >>Makefile <<-'__EOT__' + include build-aux/prelude.mk + all: without-force with-force + without-force: ; touch $@ + with-force: FORCE ; touch $@ + __EOT__ + + make + cp -a with-force with-force.bak + cp -a without-force without-force.bak + + sleep 2 + + make + ls -l + [[ with-force -nt with-force.bak ]] + [[ ! without-force -nt without-force.bak ]] + [[ ! without-force -ot without-force.bak ]] +} diff --git a/tests/prelude_go.bats b/tests/prelude_go.bats new file mode 100644 index 0000000000..d6194e0ed1 --- /dev/null +++ b/tests/prelude_go.bats @@ -0,0 +1,15 @@ +#!/usr/bin/env bats + +load common + +@test "prelude_go.mk: GOHOSTOS" { + # TODO +} + +@test "prelude_go.mk: GOHOSTARCH" { + # TODO +} + +@test "prelude_go.mk: _prelude.go.lock" { + # TODO +} diff --git a/tests/prelude_path.bats b/tests/prelude_path.bats new file mode 100644 index 0000000000..0f0e183a33 --- /dev/null +++ b/tests/prelude_path.bats @@ -0,0 +1,11 @@ +#!/usr/bin/env bats + +load common + +@test "prelude_path.mk: path.trimprefix" { + check_expr_eq echo '$(call path.trimprefix,foo/bar,foo/bar foo/bar/baz qux)' '. baz qux' +} + +@test "prelude_path.mk: path.addprefix" { + check_expr_eq echo '$(call path.addprefix,foo/bar,. baz)' 'foo/bar foo/bar/baz' +} diff --git a/tests/prelude_str.bats b/tests/prelude_str.bats new file mode 100644 index 0000000000..62be2f726c --- /dev/null +++ b/tests/prelude_str.bats @@ -0,0 +1,13 @@ +#!/usr/bin/env bats + +load common + +@test "prelude_str.mk: NL" { + # Honestly, this checks `quote.shell` more than it does NL. + check_expr_eq strict '$(NL)' $'\n' +} + +@test "prelude_str.mk: SPACE" { + # Honestly, this checks `quote.shell` more than it does SPACE. + check_expr_eq strict '$(SPACE)' ' ' +}