# Datawire build-aux Test Harness

`common.mk` includes a built-in test harness that allows you to plug
in your own tests, and have them aggregated and summarized, like:

	$ make check
	...
	PASS: go-test 5 - github.com/datawire/apro/cmd/amb-sidecar.TestAppNoToken
	PASS: go-test 6 - github.com/datawire/apro/cmd/amb-sidecar.TestAppBadToken
	PASS: go-test 7 - github.com/datawire/apro/cmd/amb-sidecar.TestAppBadCookie
	PASS: go-test 8 - github.com/datawire/apro/cmd/amb-sidecar.TestAppCallback
	PASS: go-test 9 - github.com/datawire/apro/cmd/amb-sidecar.TestAppCallbackNoCode
	PASS: tests/local/apictl.tap.gen 1 - check_version
	============================================================================
	test-suite summary
	============================================================================
	# TOTAL:  10
	# SKIP:    0
	# PASS:   10
	# XFAIL:   0
	# FAIL:    0
	# XPASS:   0
	# ERROR:   0
	============================================================================
	make[1]: Leaving directory '/home/lukeshu/src/apro'

(If you were viewing that in a terminal, it would also be pretty and
colorized.)

Each test-case can have one of 6 results:

 - `pass`, `fail`, `skip`: You can guess what these mean.
 - `xfail` ("expected fail"): The test failed, but it was expected to
   fail.  Perhaps you wrote a test for a feature for before
   implementing the feature.  Perhaps you wrote a test that captures a
   bug report, but haven't written the fix yet.  Makes
   Test-Driven-Development possible.
 - `xpass` ("unexpected pass"): The test passed, but it was expected
   to xfail.  This either means you did a poor job implementing the
   test, and it doesn't really check what you think it checks, or it
   means you've implemented the fix, but forgot to replace `xfail`
   with `fail` in the test.
 - `error`: It isn't that the test decided that there's bug in the
   code-under-test, it's that the test itself encountered an error.

You can use any test framework or language you like, as long as it can
emit [TAP, the Test Anything Protocol][TAP] (or something that can be
translated to TAP).  Both TAP version 12 and version 13 are supported.
`not ok # TODO` is "xfail", while , `ok # TODO` is "xpass"

 > Side-Note: pytest-tap emits `ok # TODO` for both xfail and xpass,
 > which is wrong, and I consider to be a bug in pytest-tap.

## Built-in test runners

By default, `common.mk` knows how to run test cases of 2 types (but
you can easily add more):

 - `*.test` GNU Automake-compatible standalone test cases.  One file
   is one test case.  `FOO.test` must be an executable file.  It is
   run with no arguments, stdout and stderr are ignored (but logged to
   `FOO.log`); it is the exit code that determines the test result:

    * 0 => pass
	* 77 => skip
	* anything else => fail

   It is not possible to xfail or xpass with this type of test.

 - `*.tap.gen` TAP-emitting test cases.  You may have multiple test
   cases per file.  `FOO.tap.gen` must be an executable file.  It is
   run with no arguments; stdout and stderr are merged, and are taken
   to be a TAP stream (both v12 and v13 are supported).  The exit code
   is ignored.

`common.mk` does *not* scan your source directory for tests (but
`go-*.mk` will scan for `go test` tests).  In your `Makefile` You must
explicitly tell it about any `.test` or `.tap.gen` files that you
would like it to include in `make check`.  For example, if you would
like it to include any `.test` or `.tap.gen` files in the `./tests/`
directory, you could write:

	test-suite.tap: $(patsubst %.test,%.tap,$(wildcard tests/*.test))
	test-suite.tap: $(patsubst %.tap.gen,%.tap,$(wildcard tests/*.tap.gen))

## Adding your own test runners

To add a new test runner, you just need a command that emits TAP:
`tee` it to a `.tap` file, and pipe that to `$(tools/tap-driver) stream -n
TEST_GROUP_NAME` to pretty-print the results as they happen:

	# Tell Make how to run the test command, and stream the results to
	# `$(tools/tap-driver) stream` to pretty-print the results as they happen.
	my-test.tap: my-test.input $(tools/tap-driver) FORCE
		@SOME_COMMAND_THAT_EMITS_TAP 2>&1 | tee $@ | $(tools/tap-driver) stream -n my-test

	# Tell Make to include 'my-test' in `make check`
	test-suite.tap: my-test.tap

For example, to use [BATS (Bash Automated Testing System)][BATS], you
would write:

	%.tap: %.bats $(tools/tap-driver) FORCE
		@bats --tap $< | tee $@ | $(tools/tap-driver) stream -n $<

	# Automatically include `./tests/*.bats`
	test-suite.tap: $(patsubst %.bats,%.tap,$(wildcard tests/*.bats))

If your test framework of choice doesn't support TAP output, you can
pipe it to a helper program that can translate it.  For example, `go
test` doesn't support TAP output, but `go test -json` output is
parsable, so we pipe that to [gotest2tap][gotest2tap], which
translates it to TAP.

If you set `SHELL = sh -o pipefail` in your `Makefile` (the pros and
cons of which I won't comment on here), you should be sure that if
your test-runner indicates success or failure with an exit code, that
you ignore that exit code:

	%.tap: %.bats $(tools/tap-driver) FORCE
		@{ bats --tap $< || true; } | tee $@ | $(tools/tap-driver) stream -n $<

## Adding dependencies of tests

It is assumed that *all* tests depend on `make build`.  To add a
dependency shared by all tests, to declare a dependency that all tests
should depend on, declare it as a dependency of `check` itself.  For
example, `common.mk` says:

	check: lint build

As another example, the `Makefile` for Ambassador Pro says:

	check: $(if $(HAVE_DOCKER),deploy proxy)

To declare a dependency for an individual test is a little trickier,
because you must keep in mind what type of test it is.  For `.tap.gen`
tests, you must declare it both on the `.tap` file and (if the
dependency is not a `.tap`) on `check` itself:

    test-suite.tap: tests/cluster/oauth-e2e.tap
	check tests/cluster/oauth-e2e.tap: tests/cluster/oauth-e2e/node_modules

If that were a `.test` test, you would need to declare it on the
`.log` file instead of `.tap`:

    test-suite.tap: tests/cluster/oauth-e2e.tap
	check tests/cluster/oauth-e2e.log: tests/cluster/oauth-e2e/node_modules

If you need one test to depend on another test, write the dependency
using the `.tap` suffix (not `.log`).  You do not need to write the
depenency for `check`, since it will already depend on the `.tap`
through `test-suite.tap`:

    test-suite.tap: foo.tap bar.tap
    foo.log: bar.tap

[TAP]: https://testanything.org
[BATS]: https://github.com/sstephenson/bats
[gotest2tap]: ../bin-go/gotest2tap/
