# Overview
# --------
#
# This is a non-recursive Makefile for running vifm tests using stic.
#
# Vifm is built before running any tests as its object files are used for tests.
# Necessary include and link directives are picked up from the main Makefile.
#
# Each test-suite (directory) is run in a separate sandbox directory. During
# compilation two defines are available:
# - TEST_DATA_PATH -- path to test-data/ directory (might be RO)
# - SANDBOX_PATH -- path to sandbox for the suite (always RW)
#
# Test suites can be run concurrently.
#
# Usage
# -----
#
# By default tests are built in release mode.
#
# make -- builds and runs all tests
# make build -- builds all tests without running them
# make check -- builds all tests and then runs them
# make <dir> -- runs specific test suite
# make <dir>.<name> -- runs specific fixture
#
# make DEBUG=1 ... -- builds debug version
# make DEBUG=gdb ... -- builds debug version and loads suite into gdb
# make DEBUG=rr ... -- builds debug version and loads suite into rr
# make DEBUG=valgrind ... -- builds debug version and runs it under valgrind
# (see valgrind-report file after a run)
#
# make VERBOSE=1 ... -- enables verbose printing of commands
#
# make TEST_RUN_PREFIX=wine -- sets run command prefix
#
# make clean -- removes various build artifacts
#
# make TESTS_CFLAGS=... TESTS_LDFLAGS... -- prepend something to CFLAGS/LDFLAGS
#
# "B" variable might be set to build tree root to run tests out of the source
# tree.
# don't undefine $TERM if we're going to invoke gdb or it will prevent its TUI
# from starting
ifneq ($(DEBUG),gdb)
# don't pass $TERM to tests, they should work without a terminal and this
# variable
unexport TERM
endif
# hide terminal multiplexers and graphic systems, so tests behave consistently
unexport STY TMUX DISPLAY WAYLAND_DISPLAY
# determine kind of OS that is running
ifeq ($(OS),Windows_NT)
ifeq ($(shell uname -o 2> /dev/null),Cygwin)
unix_env := true
else
win_env := true
endif
else
ifeq ($(CROSS),)
ifeq ($(shell id -u),0)
$(warning warning: running tests as root disables chmod-based ones)
endif
unix_env := true
else
win_env := true
endif
endif
ifdef DEBUG
BINSUBDIR := debug/
endif
# test for a bad directory path which breaks the Makefile
ifndef win_env
# testing for # seems to work only for some versions of GNU Make
ifneq (,$(findstring :,$(PWD)))
$(error Paths with : break tests' Makefile)
endif
endif
# path to build tree
B ?=
# path to storage for intermediate build files
BUILD := $(B)bin/build/$(BINSUBDIR)
# engine
suites += abbrevs autocmds cmds commands completion keys options parsing
suites += text_buffer variables
# io
suites += ioeta ionotif iop ior
# ui
suites += colmgr column_view viewcolumns_parser
# everything else
suites += bmarks escape fileops filetype filter lua menus misc undo utils
# these are built, but not automatically executed
apps := fuzz io_tester_app regs_shmem_app
# obtain list of sources that are being tested
vifm_src := ./ cfg/ compat/ engine/ int/ io/ io/private/ lua/ lua/lua/ menus/
vifm_src += modes/ modes/dialogs/ ui/ utils/
vifm_src := $(wildcard $(addprefix ../src/, $(addsuffix *.c, $(vifm_src))))
vifm_src := $(filter-out %/tags.c %/xxhash.c, $(vifm_src))
# filter out generally non-testable or sources for another platform
vifm_src := $(filter-out %/src/./vifm.c %/win_helper.c, $(vifm_src))
ifndef unix_env
vifm_src := $(filter-out %/desktop.c %/media_menu.c %/mntent.c %_nix.c, \
$(vifm_src))
endif
ifndef win_env
vifm_src := $(filter-out %/volumes_menu.c %_win.c, $(vifm_src))
endif
vifm_obj := $(B)../src/./tags.o $(vifm_src:%.c=$(B)%.o)
tests_lib := $(BUILD)libtest-support.a
tests_lib_src := $(wildcard test-support/*.c)
tests_lib_obj := $(patsubst %.c,$(BUILD)%.o,$(tests_lib_src))
# make sure that there is one compile_info.c object file in the list
vifm_obj := $(filter-out %/compile_info.o, $(vifm_obj))
vifm_obj += $(B)../src/compile_info.o
ifdef unix_env
make_args := -C $(B)../src/
endif
ifdef win_env
make_args := -C $(B)../src/ -f $(abspath ../src/Makefile.win)
exe_suffix := .exe
endif
CC = $(CROSS)gcc
AR = $(CROSS)ar
# handling of verbosity (non-verbose by default)
ifdef VERBOSE
V = 1
else
V = 0
endif
ACTUAL_CC := $(CC)
ACTUAL_AR := $(AR)
CC_0 = @echo "Compiling $@..."; $(ACTUAL_CC)
CC_1 = $(ACTUAL_CC)
AR_0 = @echo "Archiving $@..."; $(ACTUAL_AR)
AR_1 = $(ACTUAL_AR)
LD_0 = @echo "Linking $@..."; $(ACTUAL_CC)
LD_1 = $(ACTUAL_CC)
AT_0 = @
AT_1 =
# redefine commands according to verbosity state
override CC = $(CC_$(V))
override AR = $(AR_$(V))
override LD = $(LD_$(V))
AT = $(AT_$(V))
# setup compile and link flags (partially depends on OS)
override CFLAGS := $(TESTS_CFLAGS) -MMD -MP -pipe \
-Wall -Wno-char-subscripts \
-Itest-support/ -Itest-support/stic/ \
-include $(B)../build-aux/config.h \
-DTEST
override LDFLAGS := $(TESTS_LDFLAGS)
ifdef unix_env
MF := $(abspath $(B)../src/Makefile)
ifneq ($(wildcard $(MF)),)
override LDFLAGS += \
$(shell grep -m1 'LIBS =' $(MF) | sed 's/^[^=]*=//') \
$(shell grep -m1 'LDFLAGS =' $(MF) | sed 's/^[^=]*=//')
override CFLAGS += \
$(shell grep -m1 'CPPFLAGS =' $(MF) | sed 's/^[^=]*=//') \
$(shell grep -m1 'TESTS_CFLAGS =' $(MF) | sed 's/^[^=]*=//') \
$(shell grep -m1 'SANITIZERS_CFLAGS =' $(MF) | sed 's/^[^=]*=//')
endif
override CFLAGS += -I/usr/include/ncursesw
export UBSAN_OPTIONS := halt_on_error=1
endif
ifdef win_env
override CFLAGS += -pthread
override LDFLAGS += \
$(shell sed -n '/LIBS :=/{s/^[^=]\+=//p;q}' ../src/Makefile.win)
# this part is in conditional of ../src/Makefile.win
ifeq ($(OS),Windows_NT)
override LDFLAGS += -lpdcurses
else
override LDFLAGS += -lcurses
endif
endif
override CFLAGS += -fcommon
ifeq (,$(findstring -lpthread,$(LDFLAGS)))
override LDFLAGS += -pthread
endif
# work around clang
ifeq (,$(findstring clang,$(shell $(ACTUAL_CC) --version)))
ifneq (,$(findstring --coverage, $(LDFLAGS)))
# clang is inconvenient with regard to this flag, don't do coverage with
# it
override CFLAGS += --coverage
override LDFLAGS += --coverage
endif
# don't precompile header with clang (on OS X gcc is likely to be a symlink
# to clang) because it handles macros in a different way
GCH_DEP := $(BUILD)test-support/stic/stic.h.gch
# this flag seems to be gcc-specific and clang freaks out on it with -Werror
override CFLAGS += -Wno-format-truncation
endif
ifdef DEBUG
override CFLAGS += -g -O0
override LDFLAGS += -g
ifdef unix_env
override LDFLAGS += -rdynamic
else
override CFLAGS += -gdwarf-3
endif
ifeq ($(DEBUG),gdb)
TEST_RUN_PREFIX := gdb -q --args
endif
ifeq ($(DEBUG),rr)
TEST_RUN_PREFIX := rr record
endif
ifeq ($(DEBUG),valgrind)
TEST_RUN_PREFIX := valgrind --track-origins=yes --leak-check=full \
--log-file=valgrind-report --error-exitcode=5 \
--suppressions=$(abspath .)/../src/.valgrind.supp
TEST_RUN_POST := || (e=$$$$?; if [ $$$$e -eq 5 ]; then \
cat valgrind-report; \
fi; \
exit $$$$e)
endif
endif
.PHONY: check build clean $(suites) $(apps) appsources
# check and build targets are defined mostly in suite_template
check: build
@if [ "$$(find "$(SANDBOX_PATH)" -mindepth 2 | wc -l)" -ne 0 ]; then \
echo "ERROR: sandbox isn't empty"; \
find $(SANDBOX_PATH) -mindepth 2; \
false; \
fi
clean:
$(RM) -r $(B)bin/ $(SANDBOX_PATH)/
# disable implicit rules to prevent compiling main sources here
.SUFFIXES:
# sources target groups dependencies to avoid invoking nested make per target
$(vifm_obj) ../src/./tags.c: appsources
@# this receipt is needed to make Make recognize that targets were updated
appsources: $(vifm_src)
$(MAKE) $(make_args) vifm$(exe_suffix)
$(BUILD)test-support/%.o: test-support/%.c | $(BUILD)test-support
$(CC) -c -o $@ $(CFLAGS) $<
$(BUILD)test-support/stic/stic.o: test-support/stic/stic.c \
| $(BUILD)test-support/stic
$(CC) -c -o $@ $(CFLAGS) $<
$(BUILD)test-support/stic/stic.h.gch: test-support/stic/stic.h \
| $(BUILD)test-support/stic
$(CC) -c -o $@ $(CFLAGS) $<
$(tests_lib): $(BUILD)test-support/stic/stic.o $(tests_lib_obj) \
| $(BUILD)
$(AR) cr $@ $^
$(BUILD) $(B)bin/$(BINSUBDIR) $(BUILD)test-support $(BUILD)test-support/stic:
$(AT)mkdir -p $@
# this function of two arguments (array and element) returns index of the
# element in the array
pos = $(strip $(eval T := ) \
$(eval i := 0) \
$(foreach elem, $1, \
$(if $(filter $2,$(elem)), \
$(eval i := $(words $T)), \
$(eval T := $T $(elem)))) \
$i)
ifeq (,$(findstring wine,$(TEST_RUN_PREFIX)))
ifeq ($B,)
SANDBOX_PATH := $(abspath .)/sandbox
else
SANDBOX_PATH := $(B)sandbox
endif
TEST_DATA_PATH := $(abspath .)/test-data
else
SANDBOX_PATH := sandbox
TEST_DATA_PATH := test-data
endif
ifneq ($(shell find "$(SANDBOX_PATH)" -mindepth 2 2> /dev/null | wc -l),0)
$(shell $(RM) -r $(SANDBOX_PATH))
endif
# suite definition template, takes single argument: name of the suite
define suite_template
$1.src := $$(sort $$(wildcard $1/*.c))
$1.obj := $$($1.src:%.c=$(BUILD)%.o)
$1.bin := $(B)bin/$(BINSUBDIR)$1$(exe_suffix)
$1.fixtures := $$(subst /,.,\
$$(patsubst %.c,%,$$(filter-out %/suite.c,$$($1.src))))
deps += $$($1.obj:.o=.d)
$$($1.bin): $$($1.obj) $(vifm_obj) $(tests_lib) | $(B)bin/$(BINSUBDIR)
$$(LD) -o $$@ $$^ $(LDFLAGS)
$(BUILD)$1/%.o: $1/%.c $(GCH_DEP) $(BUILD)$1/filelist \
| $(BUILD)$1 $(SANDBOX_PATH)/$1
$$(CC) -c -o $$@ $(CFLAGS) -include test-support/stic/stic.h \
-DTESTID=$$(call pos, $$($1.obj), $$@) \
-DMAXTESTID=$$(words $$($1.obj)) $$< \
-DSUITE_NAME="$1" \
-DTEST_DATA_PATH='"$(TEST_DATA_PATH)"' \
-DSANDBOX_PATH='"$(SANDBOX_PATH)/$1"' \
$(BUILD)$1/filelist: $1/. | $(BUILD)$1
@if [ ! -f "$$@" -o "$$$$(cat $$@ 2> /dev/null)" != '$$($1.src)' ]; then \
echo -n '$$($1.src)' > $$@; \
fi
$(BUILD)$1:
$(AT)mkdir -p $$@
$(SANDBOX_PATH)/$1:
ifeq ($(SANDBOX_PATH),sandbox)
$(AT)mkdir -p $(B)$$@
else
$(AT)mkdir -p $$@
endif
$1: $$($1.bin)
ifeq ($B,)
@$(TEST_RUN_PREFIX) $$< -s $(TEST_RUN_POST)
else
@cd $B && $(TEST_RUN_PREFIX) $$< -s $(TEST_RUN_POST)
endif
# this runs separate fixtures, targets are of the form dir.name
.PHONY: $$($1.fixtures)
$$($1.fixtures): $$($1.bin)
ifeq ($B,)
@$(TEST_RUN_PREFIX) $$^ -s -f $$(subst .,/,$$@).c $(TEST_RUN_POST)
else
@cd $B && $(TEST_RUN_PREFIX) $$^ -s -f $$(subst .,/,$$@).c $(TEST_RUN_POST)
endif
build: $$($1.bin)
# don't add apps to the list of things to run automatically
ifeq (,$(filter $1,$(apps)))
check: $1
endif
endef
# walk throw list of suites+apps and instantiate template for each one
$(foreach suite, $(suites) $(apps), $(eval $(call suite_template,$(suite))))
# extra dependencies between test fixtures
misc: | $(regs_shmem_app.bin)
lua: | $(io_tester_app.bin)
# import dependencies calculated by the compiler
include $(wildcard $(deps) \
$(BUILD)test-support/stic/stic.h.d \
$(BUILD)test-support/*.d)