# from https://github.com/box/Makefile.test
# `make -C test -j`

# Makefile that has a convenient check target.
# It can be included from another Makefile that only has a TESTS variable
# defined like this
#
# TESTS ?=
#
# Runs the specified test executables. Prepends the test's name to each test's output
# and gives a nice summary at the end of test execution about passed failed
# tests.

# Only bash is supported
SHELL := /bin/bash

THIS_FILE := $(realpath $(lastword $(MAKEFILE_LIST)))
# The directory where Makefile.test (this file) resides
THIS_FILE_DIR := $(shell dirname $(THIS_FILE))

# FIRST_MAKEFILE may be passed from parent make to child make. If it is not
# absent, do not overwrite it.
FIRST_MAKEFILE ?= $(realpath $(firstword $(MAKEFILE_LIST)))
export FIRST_MAKEFILE

# The directory where the Makefile, that is invoked from the command line,
# resides. That makefile would define the TESTS variable. We assume that the
# binaries defined in the TESTS variable also reside in the directory as
# the Makefile. The generated intermediate files will also go to this directory.
FIRST_MAKEFILE_DIR ?= $(shell dirname $(FIRST_MAKEFILE))
export FIRST_MAKEFILE_DIR

# So that the child makefiles can see the same TESTS variable.
export TESTS

failedTestsName := .makefile_test_failed_tests
executedTestsName := .makefile_test_executed_tests
TEST_TARGETS := $(TESTS:%=TARGET_FOR_%)
export TEST_TARGETS

# If the tests need a different environment one can append to this variable.
TEST_ENVIRONMENT = PYTHONPATH=$(THIS_FILE_DIR):$$PYTHONPATH PATH=$(THIS_FILE_DIR):$$PATH

# TODO: Only write to intermediate files, if they exist already.
# https://unix.stackexchange.com/q/405497/212862
# There is still a race condition here. Maybe we should use sed for appending.
define RUN_ONE_TEST
TARGET_FOR_$(1): $$(FIRST_MAKEFILE_DIR)/$(1)
	+@export PATH=$$$$(pwd):$$$$PATH; \
          if [ -e $$(FIRST_MAKEFILE_DIR)/$$(executedTestsName) ]; then \
             echo $$< >> $$(FIRST_MAKEFILE_DIR)/$$(executedTestsName); \
          fi; \
          $$(TEST_ENVIRONMENT) $$< 2>&1 | sed "s/^/  [$$$$(basename $$<)] /"; test $$$${PIPESTATUS[0]} -eq 0; \
          if [ $$$$? -eq 0 ]; then \
             echo " PASSED: $$$$(basename $$<)"; \
          else \
             echo " FAILED: $$$$(basename $$<)"; \
             if [ -e $$(FIRST_MAKEFILE_DIR)/$$(failedTestsName) ]; then \
                echo $$< >> $$(FIRST_MAKEFILE_DIR)/$$(failedTestsName); \
             fi; \
          fi;
endef

# Build the above rule to run one test, for all tests.
$(foreach currtest,$(TESTS),$(eval $(call RUN_ONE_TEST,$(currtest))))

# execute the tests and look at the generated temp files afterwards.
actualCheck: $(TEST_TARGETS)
	+@failed_tests=$$(cat $(FIRST_MAKEFILE_DIR)/$(failedTestsName) 2> /dev/null | wc -l;); \
          executed_tests=$$(cat $(FIRST_MAKEFILE_DIR)/$(executedTestsName) 2> /dev/null | wc -l;); \
          if [ $$failed_tests -ne 0 -a $$executed_tests -ne 0 ]; then \
             echo ---------------------------------; \
             echo "Failed $$failed_tests out of $$executed_tests tests"; \
             echo ---------------------------------; \
          elif [ $$failed_tests -eq 0 ]; then  \
             echo ---------------------------------; \
             echo "All $$executed_tests tests passed"; \
             echo ---------------------------------; \
          fi; \
          exit $$failed_tests;

# A commonly used bash command to clean intermediate files. Instead of writing
# it every time re-use this variable.
RM_INTERMEDIATE_FILES := rm -f $(FIRST_MAKEFILE_DIR)/$(failedTestsName) $(FIRST_MAKEFILE_DIR)/$(executedTestsName)

# At the start of the make, we want to start with empty intermediate files.
TRUNCATE_INTERMEDIATE_FILES := cat /dev/null > $(FIRST_MAKEFILE_DIR)/$(failedTestsName) && cat /dev/null > $(FIRST_MAKEFILE_DIR)/$(executedTestsName)

# With trap make sure the clean step is always executed before and after the
# tests run time. Do not leave residual files in the repo.
check:
	+@trap "code=\$$?; \
           $(RM_INTERMEDIATE_FILES); \
           exit \$${code};" EXIT; \
          $(TRUNCATE_INTERMEDIATE_FILES); \
          $(MAKE) -f $(THIS_FILE) actualCheck;

all: check

.PHONY: all check preCheck actualCheck $(TEST_TARGETS)
.DEFAULT_GOAL := all