# Do not let mess "cd" with user-defined paths. CDPATH:= TEST_SHELL:=$(shell command -v bash 2>/dev/null) ifeq ($(TEST_SHELL),) $(error Could not determine TEST_SHELL (defaults to bash)) endif # This is expected in tests. TEST_VIM_PREFIX:=SHELL=$(TEST_SHELL) SHELL:=$(TEST_SHELL) -o pipefail # Use nvim if it is installed, otherwise vim. ifeq ($(TEST_VIM),) ifeq ($(shell command -v nvim 2>/dev/null),) TEST_VIM:=vim else TEST_VIM:=nvim endif endif IS_NEOVIM=$(findstring nvim,$(TEST_VIM))$(findstring neovim,$(TEST_VIM)) test: $(if $(IS_NEOVIM),testnvim,testvim) test_interactive: $(if $(TEST_VIM),$(if $(IS_NEOVIM),testnvim_interactive,testvim_interactive),testnvim_interactive testvim_interactive) VADER:=Vader! VADER_OPTIONS:=-q VADER_ARGS=tests/all.vader VIM_ARGS='+$(VADER) $(VADER_OPTIONS) $(VADER_ARGS)' NEOMAKE_TESTS_DEP_PLUGINS_DIR?=build/vim/plugins TESTS_VADER_DIR:=$(NEOMAKE_TESTS_DEP_PLUGINS_DIR)/vader $(TESTS_VADER_DIR): mkdir -p $(dir $@) git clone -q --depth=1 -b display-source-with-exceptions https://github.com/blueyed/vader.vim $@ TESTS_FUGITIVE_DIR:=$(NEOMAKE_TESTS_DEP_PLUGINS_DIR)/vim-fugitive $(TESTS_FUGITIVE_DIR): mkdir -p $(dir $@) git clone -q --depth=1 https://github.com/tpope/vim-fugitive $@ DEP_PLUGINS=$(TESTS_VADER_DIR) $(TESTS_FUGITIVE_DIR) TEST_VIMRC:=tests/vim/vimrc testwatch: override export VADER_OPTIONS+=-q testwatch: contrib/run-tests-watch testwatchx: override export VADER_OPTIONS+=-x testwatchx: testwatch testx: override VADER_OPTIONS+=-x testx: test testnvimx: override VADER_OPTIONS+=-x testnvimx: testnvim testvimx: override VADER_OPTIONS+=-x testvimx: testvim # Set Neovim logfile destination to prevent `.nvimlog` being created. testnvim: export NVIM_LOG_FILE:=/dev/stderr testnvim: TEST_VIM:=nvim testnvim: TEST_VIM_PREFIX+=VADER_OUTPUT_FILE=/dev/stderr testnvim: | build/vim-test-home $(DEP_PLUGINS) $(call func-run-vim) testvim: TEST_VIM:=vim testvim: | build/vim-test-home $(DEP_PLUGINS) $(call func-run-vim) # Add coloring to Vader's output: # 1. failures (includes pending) in red "(X)" # 2. test case header in bold "(2/2)" # 3. Neomake's debug log messages in less intense grey # 4. non-Neomake log lines (e.g. from :Log) in bold/bright yellow. _SED_HIGHLIGHT_ERRORS:=| contrib/highlight-log --compact vader # Need to close stdin to fix spurious 'sed: couldn't write X items to stdout: Resource temporarily unavailable'. # NOTE: uses &1 >/dev/null Vim: Error reading input, exiting... # > Vim: Finished. # For Vim `-s /dev/null` is used to skip the 2s delay with warning # "Vim: Warning: Output is not to a terminal". COVERAGE_FILE:=.coverage_covimerage _COVIMERAGE=$(if $(filter-out 0,$(NEOMAKE_DO_COVERAGE)),covimerage run --data-file $(COVERAGE_FILE) --append --no-report ,) define func-run-vim $(info Using: $(shell $(TEST_VIM_PREFIX) "$(TEST_VIM)" --version | head -n2)) ($(_COVIMERAGE)$(if $(TEST_VIM_PREFIX),env $(TEST_VIM_PREFIX) ,)"$(TEST_VIM)" \ $(if $(IS_NEOVIM),$(if $(_REDIR_STDOUT),--headless,),-X $(if $(_REDIR_STDOUT),-s /dev/null,)) \ --noplugin -Nu $(TEST_VIMRC) -i NONE $(VIM_ARGS) $(_REDIR_STDOUT)) $(_SED_HIGHLIGHT_ERRORS) endef # Interactive tests, keep Vader open. _run_interactive: VADER:=Vader _run_interactive: _REDIR_STDOUT:= _run_interactive: $(call func-run-vim) testvim_interactive: TEST_VIM:=vim -X testvim_interactive: _run_interactive testnvim_interactive: TEST_VIM:=nvim testnvim_interactive: _run_interactive # Manually invoke Vim, using the test setup. This helps with building tests. runvim: VIM_ARGS:= runvim: testvim_interactive runnvim: VIM_ARGS:= runnvim: testnvim_interactive # Add targets for .vader files, absolute and relative. # This can be used with `b:dispatch = ':Make %'` in Vim. TESTS:=$(wildcard tests/*.vader tests/*/*.vader) uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1))) _TESTS_REL_AND_ABS:=$(call uniq,$(abspath $(TESTS)) $(TESTS)) FILE_TEST_TARGET=$(if $(IS_NEOVIM),testnvim,testvim) $(_TESTS_REL_AND_ABS): $(MAKE) --no-print-directory $(FILE_TEST_TARGET) VADER_ARGS='$@' .PHONY: $(_TESTS_REL_AND_ABS) testcoverage: COVERAGE_VADER_ARGS:=tests/main.vader $(wildcard tests/isolated/*.vader) testcoverage: $(RM) $(COVERAGE_FILE) @ret=0; \ for testfile in $(COVERAGE_VADER_ARGS); do \ $(MAKE) --no-print-directory test VADER_ARGS=$$testfile NEOMAKE_DO_COVERAGE=1 || (( ++ret )); \ done; \ exit $$ret tags: ctags -R --langmap=vim:+.vader .PHONY: tags # Linters, called from .travis.yml. LINT_ARGS:=./plugin ./autoload # Vint. VINT_BIN=$(shell command -v vint 2>/dev/null || echo build/vint/bin/vint) build/vint: | build $(shell command -v virtualenv 2>/dev/null || echo python3 -m venv) $@ build/vint/bin/vint: | build/vint build/vint/bin/pip install --quiet vim-vint vint: | $(VINT_BIN) $| --color $(LINT_ARGS) vint-errors: | $(VINT_BIN) $| --color --error $(LINT_ARGS) # vimlint VIMLINT_BIN=$(shell command -v vimlint 2>/dev/null || echo build/vimlint/bin/vimlint.sh -l build/vimlint -p build/vimlparser) build/vimlint/bin/vimlint.sh: build/vimlint build/vimlparser build/vimlint: | build git clone -q --depth=1 https://github.com/syngan/vim-vimlint $@ build/vimlparser: | build git clone -q --depth=1 https://github.com/ynkdir/vim-vimlparser $@ VIMLINT_OPTIONS=-u -e EVL102.l:_=1 -e 'EVL103.a:_.*=1' vimlint: | $(firstword $(VIMLINT_BIN)) $(VIMLINT_BIN) $(VIMLINT_OPTIONS) $(LINT_ARGS) vimlint-errors: | $(firstword VIMLINT_BIN) $(VIMLINT_BIN) $(VIMLINT_OPTIONS) -E $(LINT_ARGS) build build/vim-test-home: mkdir $@ build/vim-test-home: | build build/vimhelplint: | build cd build \ && wget -O- https://github.com/machakann/vim-vimhelplint/archive/master.tar.gz \ | tar xz \ && mv vim-vimhelplint-master vimhelplint vimhelplint: | $(if $(VIMHELPLINT_DIR),,build/vimhelplint) contrib/vimhelplint doc/neomake.txt # Run tests in dockerized Vims. DOCKER_REPO:=neomake/vims-for-tests DOCKER_TAG:=50 NEOMAKE_DOCKER_IMAGE?= DOCKER_IMAGE:=$(if $(NEOMAKE_DOCKER_IMAGE),$(NEOMAKE_DOCKER_IMAGE),$(DOCKER_REPO):$(DOCKER_TAG)) DOCKER_STREAMS:=-ti DOCKER_ARGS:= DOCKER=docker run $(DOCKER_STREAMS) --rm \ -v $(PWD):/testplugin \ -w /testplugin \ -e NEOMAKE_TEST_NO_COLORSCHEME \ $(DOCKER_ARGS) $(DOCKER_IMAGE) docker_image: docker build -f Dockerfile.tests -t $(DOCKER_REPO):$(DOCKER_TAG) . docker_push: docker push $(DOCKER_REPO):$(DOCKER_TAG) docker_update_latest: docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REPO):latest docker push $(DOCKER_REPO):latest docker_update_image: @git diff --cached --exit-code >/dev/null || { echo "WARN: git index is not clean."; } @if git diff --exit-code -- Makefile >/dev/null; then \ sed -i '/^DOCKER_TAG:=/s/:=.*/:=$(shell echo $$(($(DOCKER_TAG)+1)))/' Makefile; \ else \ echo "WARN: Makefile is not clean. Not updating."; \ fi @if git diff --exit-code -- Dockerfile.tests >/dev/null; then \ sed -i '/^ENV NEOMAKE_DOCKERFILE_UPDATE=/s/=.*/=$(shell date +%Y-%m-%d)/' Dockerfile.tests; \ else \ echo "WARN: Dockerfile.tests is not clean. Not updating."; \ fi make docker_image make docker_test DOCKER_VIM=vim81 @echo "Done. Use 'make docker_push' to push it, and then update .circleci/config.yml." DOCKER_VIMS:=vim73 vim74-trusty vim74-xenial vim80 vim81 \ neovim-v0.1.7 neovim-v0.3.8 neovim-master vim-master _DOCKER_VIM_TARGETS:=$(addprefix docker_test-,$(DOCKER_VIMS)) docker_test_all: $(_DOCKER_VIM_TARGETS) $(_DOCKER_VIM_TARGETS): $(MAKE) docker_test DOCKER_VIM=$(patsubst docker_test-%,%,$@) _docker_test: DOCKER_VIM:=vim81 _docker_test: DOCKER_MAKE_TARGET=$(DOCKER_MAKE_TEST_TARGET) \ TEST_VIM='/vim-build/bin/$(DOCKER_VIM)' \ VADER_OPTIONS="$(VADER_OPTIONS)" VADER_ARGS="$(VADER_ARGS)" \ _REDIR_STDOUT="$(_REDIR_STDOUT)" \ $(DOCKER_MAKE_TEST_ARGS) _docker_test: docker_make docker_test: DOCKER_MAKE_TEST_TARGET:=test docker_test: DOCKER_STREAMS:=-t docker_test: _docker_test docker_test_interactive: DOCKER_MAKE_TEST_TARGET:=test_interactive docker_test_interactive: DOCKER_STREAMS:=-ti docker_test_interactive: _docker_test docker_testcoverage: DOCKER_MAKE_TEST_TARGET:=testcoverage # Pick up VADER_ARGS from command line for COVERAGE_VADER_ARGS. docker_testcoverage: DOCKER_MAKE_TEST_ARGS:=$(if $(filter command line,$(origin COVERAGE_VADER_ARGS)),COVERAGE_VADER_ARGS="$(COVERAGE_VADER_ARGS)",$(if $(filter command line,$(origin VADER_ARGS)),COVERAGE_VADER_ARGS="$(VADER_ARGS)",)) docker_testcoverage: DOCKER_STREAMS:=-t docker_testcoverage: _docker_test sed -i 's~/testplugin/~$(CURDIR)/~g' $(COVERAGE_FILE) coverage report -m docker_run: DOCKER_ARGS:=-e PATH=/vim-build/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin docker_run: $(DEP_PLUGINS) docker_run: $(DOCKER) $(if $(DOCKER_RUN),$(DOCKER_RUN),bash) # Pass down/through MAKEFLAGS explicitly (not available in Docker env). docker_make: DOCKER_RUN=$(MAKE) -$(MAKEFLAGS) $(DOCKER_MAKE_TARGET) docker_make: docker_run docker_check: DOCKER_MAKE_TARGET=check_docker docker_check: docker_make docker_vimhelplint: $(MAKE) docker_make DOCKER_MAKE_TARGET=vimhelplint _ECHO_DOCKER_VIMS:=ls /vim-build/bin | grep vim | sort docker_list_vims: docker run --rm $(DOCKER_IMAGE) $(_ECHO_DOCKER_VIMS) check_lint_diff: @# NOTE: does not see changed files for builds on master. @set -e; \ echo "Looking for changed files (to origin/master)."; \ CHANGED_VIM_FILES=($$(git diff-tree --no-commit-id --name-only --diff-filter=AM -r origin/master.. \ | grep '\.vim$$' | grep -v '^tests/fixtures')) || true; \ ret=0; \ if [ "$${#CHANGED_VIM_FILES[@]}" -eq 0 ]; then \ echo 'No .vim files changed.'; \ else \ MAKE_ARGS="LINT_ARGS=$${CHANGED_VIM_FILES[*]}"; \ echo "== Running \"make vimlint $$MAKE_ARGS\" =="; \ $(MAKE) --no-print-directory vimlint "$$MAKE_ARGS" || (( ret+=1 )); \ echo "== Running \"make vint $$MAKE_ARGS\" =="; \ $(MAKE) --no-print-directory vint "$$MAKE_ARGS" || (( ret+=2 )); \ fi; \ if ! git diff-tree --quiet --exit-code --diff-filter=AM -r origin/master.. -- doc/neomake.txt; then \ echo "== Running \"make vimhelplint\" for changed doc/neomake.txt =="; \ $(MAKE) --no-print-directory vimhelplint || (( ret+=4 )); \ fi; \ exit $$ret check_lint: vimlint vint vimhelplint # Checks to be run within the Docker image. check_docker: @:; set -e; ret=0; \ echo '== Checking for DOCKER_VIMS to be in sync'; \ vims=$$($(_ECHO_DOCKER_VIMS)); \ docker_vims="$$(printf '%s\n' $(DOCKER_VIMS) | sort)"; \ if ! [ "$$vims" = "$$docker_vims" ]; then \ echo "DOCKER_VIMS is out of sync with Vims in image."; \ diff <(echo "$$vims") <(echo "$$docker_vims"); \ (( ret+=8 )); \ fi; \ exit $$ret # Like CircleCI runs them. check_in_docker: DOCKER_MAKE_TARGET=checkqa check_in_docker: docker_make # Run in CircleCI. checkqa: $(MAKE) -k check check_docker check_lint_diff check: @:; set -e; ret=0; \ [ $$TERM = dumb ] && export TERM=xterm; \ echo_bold() { tput bold; echo "$$@"; tput sgr0; }; \ echo '== Checking that all tests are included'; \ for f in $(filter-out all.vader main.vader isolated.vader,$(notdir $(shell git ls-files tests/*.vader))); do \ if ! grep -q "^Include.*: $$f" tests/main.vader; then \ echo_bold "Test not included in main.vader: $$f" >&2; ret=1; \ fi; \ done; \ for f in $(notdir $(shell git ls-files tests/isolated/*.vader)); do \ if ! grep -q "^Include.*: isolated/$$f" tests/isolated.vader; then \ echo_bold "Test not included in isolated.vader: $$f" >&2; ret=1; \ fi; \ done; \ echo '== Checking for absent Before sections in tests'; \ if grep '^Before:' tests/*.vader; then \ echo_bold "Before: should not be used in tests itself, because it overrides the global one."; \ (( ret+=2 )); \ fi; \ echo '== Checking for absent :Log calls'; \ if git --no-pager grep --line-number --color --perl-regexp '^(\s*au.*\b)?\s*Log\b' \ -- :^tests/include/init.vim :^tests/include/setup.vader; then \ echo_bold "Found Log commands."; \ (( ret+=4 )); \ fi; \ echo '== Checking tests'; \ output="$$(grep --line-number --color AssertThrows -A1 tests/*.vader)"; \ output="$(echo "$$output" \ | grep -E '^[^[:space:]]+- ' \ | grep -v g:vader_exception | sed -e s/-/:/ -e s/-// || true)"; \ if [[ -n "$$output" ]]; then \ echo_bold 'AssertThrows used without checking g:vader_exception:' >&2; \ echo "$$output" >&2; \ (( ret+=16 )); \ fi; \ echo '== Running custom checks'; \ tput bold; \ contrib/vim-checks $(LINT_ARGS) || (( ret+= 16 )); \ tput sgr0; \ exit $$ret NEOMAKE_LOG:=/tmp/neomake.log tail_log: fifo=$(shell mktemp -u); \ mkfifo $$fifo; \ tail -f $(NEOMAKE_LOG) \ | $(CURDIR)/contrib/highlight-log >> $$fifo & \ less --force --chop-long-lines +F $$fifo clean: $(RM) -r build .PHONY: clean # Fixtures {{{ # A function to define targets/rules for fixture generation. # Args: # 1: the tool (bin) and input/output dir. # 2: arguments for the tool # NOTE: uses "sed -i.bak" for MacOS's default sed. define func-generate-fixture tests/fixtures/output/$1: mkdir -p $$@ tests/fixtures/output/$1/%.stderr tests/fixtures/output/$1/%.stdout tests/fixtures/output/$1/%.exitcode: tests/fixtures/input/$1/% | tests/fixtures/output/$1 @baseout=tests/fixtures/output/$1/$$*; \ echo "Generating $$$$baseout"; \ $1 $2 $$< >$$$$baseout.stdout 2>$$$$baseout.stderr; \ ret=$$$$?; \ printf $$$$ret > $$$$baseout.exitcode; \ if ! [ -f $$$$baseout.stdout ]; then \ echo "Missing output: $$$$baseout.stdout." >&2; exit 1; \ fi; \ sed -i.bak -e "s~$(CURDIR)/~/tmp/neomake-tests/~g" \ $$$$baseout.stdout $$$$baseout.stderr; \ $(RM) $$$$baseout.stdout.bak $$$$baseout.stderr.bak endef # Call and eval the above function to generate rules for different tools. $(eval $(call func-generate-fixture,puppet,parser validate --color=false)) $(eval $(call func-generate-fixture,puppet-lint,--log-format "%{path}:%{line}:%{column}:%{kind}:[%{check}] %{message}")) $(eval $(call func-generate-fixture,xmllint,--xinclude --postvalid --noout)) $(eval $(call func-generate-fixture,zsh,-n)) _FIXTURES_INPUT:=$(wildcard tests/fixtures/input/*/*) _FIXTURES_OUTPUT:=$(patsubst tests/fixtures/input/%,tests/fixtures/output/%,$(addsuffix .stdout,$(_FIXTURES_INPUT)) $(addsuffix .stderr,$(_FIXTURES_INPUT))) fixtures: $(_FIXTURES_OUTPUT) .PHONY: fixtures fixtures-rebuild: $(RM) tests/fixtures/output/*/*.stdout tests/fixtures/output/*/*.stderr $(MAKE) --keep-going fixtures .PHONY: fixtures-rebuild # }}} .PHONY: vint vint-errors vimlint vimlint-errors .PHONY: test testnvim testvim testnvim_interactive testvim_interactive .PHONY: runvim runnvim tags _run_tests