# Makefile for org-drill # # Usage: # make help - Show this help message # make test - Run all tests (unit + robot) # make test-unit - Run ERT unit tests only # make test-file FILE=org-drill-test.el - Run specific test file # make test-name TEST=load-test - Run tests matching pattern # make robot - Run basic robot tests # make robot-all - Run all robot tests # make docker-test - Run tests in Docker (multiple Emacs versions) # make setup - Install dependencies via Eask # make clean - Remove generated files # Emacs binary to use (override with: make EMACS=emacs29 test) EMACS ?= emacs # Check for Eask in PATH or common installation location EASK ?= $(shell command -v eask 2>/dev/null || echo "$(HOME)/.local/bin/eask") # Include local overrides if present -include makefile-local ifdef EMACS EMACS_ENV=EMACS=$(EMACS) endif # Test directories and files TEST_DIR = tests # Match test-*.el pattern, excluding integration tests UNIT_TESTS = $(filter-out $(TEST_DIR)/%integration-test.el, \ $(wildcard $(TEST_DIR)/test-*.el)) INTEGRATION_TESTS = $(wildcard $(TEST_DIR)/*-integration-test.el) ALL_TESTS = $(UNIT_TESTS) $(INTEGRATION_TESTS) # Emacs batch flags EMACS_BATCH = $(EMACS) --batch --no-site-file --no-site-lisp # Docker configuration DOCKER_TAG=26 # Coverage configuration COVERAGE_DIR = .coverage COVERAGE_FILE = $(COVERAGE_DIR)/simplecov.json # Source file (single-file package) SOURCE_FILE = org-drill.el # ERT selector that excludes tests tagged :slow. Applied to the # default `make test-*' and `make coverage' loops so a slow integration # suite doesn't dominate the fast feedback path. `make test-name' is # left alone — if the user named a pattern, run it whether or not # anything matches is tagged slow. ERT_FAST_SELECTOR = (ert-run-tests-batch-and-exit '(not (tag :slow))) .PHONY: help test test-all test-unit test-integration test-file test-name setup build clean clean-elc .PHONY: robot robot-all robot-basic robot-leitner robot-all-card robot-spanish robot-explainer .PHONY: docker-test test-cp test-git .PHONY: coverage coverage-clean .PHONY: lint compile validate-parens # Default target help: @echo "org-drill Test Targets:" @echo "" @echo "Primary Targets:" @echo " make help - Show this help message" @echo " make test - Run all tests (unit + robot)" @echo " make test-unit - Run unit tests only ($(words $(UNIT_TESTS)) files)" @echo " make test-integration - Run integration tests only ($(words $(INTEGRATION_TESTS)) files)" @echo " make robot - Run basic robot tests" @echo " make robot-all - Run all robot tests" @echo "" @echo "Selective Testing:" @echo " make test-file FILE= - Run specific test file" @echo " make test-name TEST= - Run tests matching pattern" @echo "" @echo "Coverage:" @echo " make coverage - Generate simplecov JSON at $(COVERAGE_FILE)" @echo " make coverage-clean - Delete the coverage report file" @echo "" @echo "Validation & Lint:" @echo " make compile - Byte-compile $(SOURCE_FILE)" @echo " make validate-parens - Check $(SOURCE_FILE) for unbalanced parens" @echo " make lint - Run checkdoc + package-lint + elisp-lint" @echo "" @echo "Advanced Targets:" @echo " make setup - Install dependencies via Eask" @echo " make build - Byte-compile package via Eask" @echo " make docker-test - Run tests in Docker (multiple Emacs versions)" @echo " make clean - Remove generated files" @echo " make clean-elc - Remove compiled .elc files" @echo "" @echo "Examples:" @echo " make test-file FILE=org-drill-test.el" @echo " make test-name TEST=load-test" @echo " make test-name TEST='find-*'" @echo " make EMACS=emacs29 test # Use specific Emacs version" @echo "" @echo "Robot Test Targets:" @echo " make robot-basic - Run basic robot test" @echo " make robot-leitner - Run Leitner algorithm test" @echo " make robot-all-card - Run all-card robot test" @echo " make robot-spanish - Run Spanish vocabulary test" @echo " make robot-explainer - Run explainer robot test" # Default: run all tests all: robot test-unit # Install dependencies setup: @if ! command -v $(EASK) >/dev/null 2>&1; then \ echo "[✗] Eask not found. Please install Eask first:"; \ echo " https://emacs-eask.github.io/"; \ echo " Or: npm install -g @emacs-eask/cli"; \ exit 1; \ fi @echo "[i] Installing dependencies via Eask..." @$(EMACS_ENV) $(EASK) install-deps --dev @echo "[✓] Dependencies installed" # Byte-compile package build: $(EMACS_ENV) $(EASK) compile # Run all tests test: robot test-unit # Full test suite test-all: robot-all test-unit test-integration # Run unit tests only test-unit: setup @echo "[i] Running unit tests ($(words $(UNIT_TESTS)) files)..." @failed=0; \ for test in $(UNIT_TESTS); do \ echo " Testing $$test..."; \ $(EMACS_ENV) $(EASK) emacs --batch -q \ -l ert \ -l assess \ -l org-drill.el \ -l $$test \ --eval "$(ERT_FAST_SELECTOR)" || failed=$$((failed + 1)); \ done; \ if [ $$failed -eq 0 ]; then \ echo "[✓] All unit tests passed"; \ else \ echo "[✗] $$failed unit test file(s) failed"; \ exit 1; \ fi # Run integration tests only test-integration: setup @echo "[i] Running integration tests ($(words $(INTEGRATION_TESTS)) files)..." @if [ $(words $(INTEGRATION_TESTS)) -eq 0 ]; then \ echo "[i] No integration tests found"; \ else \ failed=0; \ for test in $(INTEGRATION_TESTS); do \ echo " Testing $$test..."; \ $(EMACS_ENV) $(EASK) emacs --batch -q \ -l ert \ -l assess \ -l org-drill.el \ -l $$test \ -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \ done; \ if [ $$failed -eq 0 ]; then \ echo "[✓] All integration tests passed"; \ else \ echo "[✗] $$failed integration test file(s) failed"; \ exit 1; \ fi; \ fi # Run specific test file # Usage: make test-file FILE=org-drill-test.el test-file: setup ifndef FILE @echo "[✗] Error: FILE parameter required" @echo "Usage: make test-file FILE=org-drill-test.el" @exit 1 endif @echo "[i] Running tests in $(FILE)..." @$(EMACS_ENV) $(EASK) emacs --batch -q \ -l ert \ -l assess \ -l org-drill.el \ -l $(TEST_DIR)/$(FILE) \ --eval "$(ERT_FAST_SELECTOR)" @echo "[✓] Tests in $(FILE) complete" # Run specific test by name/pattern # Usage: make test-name TEST=load-test # make test-name TEST="find-*" test-name: setup ifndef TEST @echo "[✗] Error: TEST parameter required" @echo "Usage: make test-name TEST=load-test" @echo " make test-name TEST='find-*'" @exit 1 endif @echo "[i] Running tests matching pattern: $(TEST)..." @$(EMACS_ENV) $(EASK) emacs --batch -q \ -l ert \ -l assess \ -l org-drill.el \ $(foreach test,$(ALL_TESTS),-l $(test)) \ --eval '(ert-run-tests-batch-and-exit "$(TEST)")' @echo "[✓] Tests matching '$(TEST)' complete" # # Coverage (undercover + simplecov JSON) # # Each unit-test file runs in its own Emacs process (matching `make # test-unit'); run-coverage-file.el instruments org-drill.el before the # source is loaded, and undercover merges per-file results into a single # simplecov JSON. coverage: coverage-clean setup $(COVERAGE_DIR) @echo "[i] Cleaning .elc files so undercover can instrument source..." @find . -name "*.elc" -delete @echo "[i] Running coverage across $(words $(UNIT_TESTS)) unit-test file(s)..." @echo " (slower than 'make test-unit' — each file runs in its own Emacs)" @failed=0; \ for test in $(UNIT_TESTS); do \ echo " Coverage: $$test..."; \ $(EMACS_ENV) $(EASK) emacs --batch -q \ -l ert \ -l assess \ -l $(TEST_DIR)/run-coverage-file.el \ -l org-drill.el \ -l $$test \ --eval "$(ERT_FAST_SELECTOR)" || failed=$$((failed + 1)); \ done; \ if [ $$failed -gt 0 ]; then \ echo "[!] $$failed test file(s) failed during coverage run"; \ exit 1; \ fi @if [ -f $(COVERAGE_FILE) ]; then \ echo "[✓] Coverage report: $(COVERAGE_FILE) ($$(du -h $(COVERAGE_FILE) | cut -f1))"; \ else \ echo "[!] No coverage file produced; check that undercover is installed"; \ exit 1; \ fi coverage-clean: @rm -f $(COVERAGE_FILE) $(COVERAGE_DIR): @mkdir -p $(COVERAGE_DIR) # # Validation & Lint # # Byte-compile the source file. byte-compile-error-on-warn is left # nil so existing warnings don't fail the target — match `make build' # behavior. Tighten when the warning backlog is cleared. compile: setup @echo "[i] Byte-compiling $(SOURCE_FILE)..." @$(EMACS_ENV) $(EASK) emacs --batch -q \ --eval "(progn \ (setq byte-compile-error-on-warn nil) \ (batch-byte-compile))" $(SOURCE_FILE) @echo "[✓] Compilation complete" # Fast structural check — `check-parens' surfaces the line of the # offending paren without needing a full byte-compile pass. validate-parens: @echo "[i] Checking $(SOURCE_FILE) for unbalanced parentheses..." @$(EMACS_BATCH) --eval "(condition-case err \ (progn \ (find-file \"$(SOURCE_FILE)\") \ (check-parens) \ (kill-emacs 0)) \ (error (progn \ (message \"ERROR: %s\" err) \ (kill-emacs 1))))" @echo "[✓] $(SOURCE_FILE) parens balance" # Run all three linters. Informational — does not exit non-zero on # findings, since the existing source has known docstring and style # debt to clear. Re-tighten to a hard gate after the docstring pass # in todo.org is done. lint: setup @echo "[i] Running checkdoc + package-lint + elisp-lint on $(SOURCE_FILE)..." @$(EMACS_ENV) $(EASK) emacs --batch -q \ --eval "(progn \ (require 'checkdoc) \ (require 'package-lint nil t) \ (require 'elisp-lint nil t) \ (find-file \"$(SOURCE_FILE)\") \ (when (featurep 'checkdoc) \ (message \"-- checkdoc --\") \ (checkdoc-current-buffer t)) \ (when (featurep 'package-lint) \ (message \"-- package-lint --\") \ (package-lint-current-buffer)) \ (when (featurep 'elisp-lint) \ (message \"-- elisp-lint --\") \ (elisp-lint-file \"$(SOURCE_FILE)\")))" || true @echo "[i] Lint complete (informational — no hard failure)" # # Robot Tests (Automated UI Tests) # # Basic robot test (default for quick testing) robot: robot-basic # All robot tests robot-all: robot-basic robot-leitner robot-all-card robot-spanish # Individual robot tests robot-basic: clean-elc @echo "[i] Running basic robot test..." @$(EMACS_ENV) ./robot/basic-run.sh $(SMALL) @echo "[✓] Basic robot test complete" robot-leitner: clean-elc @echo "[i] Running Leitner robot test..." @$(EMACS_ENV) ./robot/leitner-run.sh $(SMALL) @echo "[✓] Leitner robot test complete" robot-all-card: clean-elc @echo "[i] Running all-card robot test..." @$(EMACS_ENV) ./robot/all-card-run.sh $(SMALL) @echo "[✓] All-card robot test complete" robot-spanish: clean-elc @echo "[i] Running Spanish robot test..." @$(EMACS_ENV) ./robot/spanish-run.sh $(SMALL) @echo "[✓] Spanish robot test complete" robot-explainer: clean-elc @echo "[i] Running explainer robot test..." @$(EMACS_ENV) ./robot/explainer-run.sh $(SMALL) @echo "[✓] Explainer robot test complete" # # Docker Testing (Multiple Emacs Versions) # test-cp: docker run -it --rm --name docker-cp \ -v $(PWD):/usr/src/app \ -w /usr/src/app \ --entrypoint=/bin/bash \ silex/emacs:$(DOCKER_TAG)-dev ./test-by-cp test-git: docker run -it --rm --name docker-git \ -v $(PWD):/usr/src/app \ -w /usr/src/app \ --entrypoint=/bin/bash \ silex/emacs:$(DOCKER_TAG)-dev ./test-from-git docker-test: @echo "[i] Running Docker tests across multiple Emacs versions..." @$(MAKE) test-git DOCKER_TAG=27.2 @$(MAKE) test-cp DOCKER_TAG=27.2 @$(MAKE) test-git DOCKER_TAG=26.3 @$(MAKE) test-cp DOCKER_TAG=26.3 @echo "[✓] Docker tests complete" # # Cleanup # clean-elc: @echo "[i] Cleaning compiled .elc files..." @find . -name "*.elc" -delete @echo "[✓] Clean .elc complete" clean: @echo "[i] Cleaning generated files..." @find . -name "*.elc" -delete @find $(TEST_DIR) -name "*-test-*" -type f -delete @echo "[✓] Clean complete"