diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-05 04:58:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-05 04:58:34 -0500 |
| commit | 6554cb8000a8f471f7c9e050e284f3fc364d5dad (patch) | |
| tree | bdafa976a0899384e08e407465635eebfe454b77 | |
| parent | a7e2eda6f36cb490a59f6c2670839ab1e52049fc (diff) | |
| download | emacs-wttrin-6554cb8000a8f471f7c9e050e284f3fc364d5dad.tar.gz emacs-wttrin-6554cb8000a8f471f7c9e050e284f3fc364d5dad.zip | |
build: switch Makefile to eask, wire up undercover coverage
I wanted a coverage number, so I added an Eask file declaring the runtime dep (xterm-color) plus three dev deps (undercover, package-lint, elisp-lint). The Makefile now runs every test and lint recipe through `eask emacs`. That drops the hand-rolled `(require 'package)` + `add-to-list 'package-archives` boilerplate that was duplicated across six recipes.
I added a `make deps` target that runs `eask install-deps --dev`. I also added a `make coverage` target that loads `tests/run-coverage-file.el` before each unit-test file. Undercover instruments the three source files first, then the test loads pick up the instrumented copy. Per-file results merge into `.coverage/simplecov.json` in simplecov format.
I expanded `validate-parens` and `compile` to cover all three source files instead of just `wttrin.el`. Lint stays scoped to the main file for now.
Coverage right now is 84% overall: wttrin.el 92%, wttrin-geolocation.el 100%, wttrin-debug.el 27%. The debug module is low because only the integration test exercises it. The coverage loop runs unit tests only.
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Eask | 24 | ||||
| -rw-r--r-- | Makefile | 213 | ||||
| -rw-r--r-- | tests/run-coverage-file.el | 34 |
4 files changed, 196 insertions, 79 deletions
@@ -1,3 +1,7 @@ /todo.org /.ai/ /.stignore +/.eask/ +/.coverage/ +/dist/ +*.elc @@ -0,0 +1,24 @@ +;; -*- mode: eask; lexical-binding: t -*- + +(package "wttrin" + "0.3.2" + "Emacs Frontend for Service wttr.in") + +(website-url "https://github.com/cjennings/emacs-wttrin") +(keywords "weather" "wttrin" "games") + +(package-file "wttrin.el") + +(files "wttrin.el" "wttrin-debug.el" "wttrin-geolocation.el") + +(source "gnu") +(source "nongnu") +(source "melpa") + +(depends-on "emacs" "24.4") +(depends-on "xterm-color") + +(development + (depends-on "undercover") + (depends-on "package-lint") + (depends-on "elisp-lint")) @@ -2,20 +2,31 @@ # # Usage: # make help - Show this help message -# make test - Run all tests +# make test - Run all tests (smoke + unit + integration) +# make test-smoke - Run smoke tests only # make test-unit - Run unit tests only +# make test-integration - Run integration tests only # make test-file FILE=test-foo.el - Run specific test file # make test-name TEST=test-foo-* - Run tests matching pattern +# make deps - Install dependencies via Eask +# make coverage - Generate simplecov JSON at .coverage/simplecov.json # make validate-parens - Check for unbalanced parentheses # make validate - Load wttrin.el to verify it compiles -# make compile - Byte-compile wttrin.el +# make compile - Byte-compile source files # make lint - Run all linters (checkdoc, package-lint, elisp-lint) # make clean - Remove test artifacts and compiled files -# make clean-compiled - Remove .elc/.eln files only -# make clean-tests - Remove test artifacts only # Emacs binary to use (override with: make EMACS=emacs29 test) EMACS ?= emacs +# Eask binary (override if installed elsewhere) +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 # Directories TEST_DIR = tests @@ -29,18 +40,22 @@ ALL_TESTS = $(SMOKE_TESTS) $(UNIT_TESTS) $(INTEGRATION_TESTS) # Source files MAIN_FILE = wttrin.el +SOURCE_FILES = wttrin.el wttrin-debug.el wttrin-geolocation.el TEST_UTIL_FILES = $(wildcard $(TEST_DIR)/testutil-*.el) -# Emacs batch flags +# Coverage configuration +COVERAGE_DIR = .coverage +COVERAGE_FILE = $(COVERAGE_DIR)/simplecov.json + +# Plain emacs invocation (no package archives, used for parens-check) EMACS_BATCH = $(EMACS) --batch --no-site-file --no-site-lisp -EMACS_TEST = $(EMACS_BATCH) \ - --eval "(require 'package)" \ - --eval "(add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)" \ - --eval "(package-initialize)" \ - -L $(PROJECT_ROOT) -L $(TEST_DIR) + +# Eask-managed emacs invocation (deps on load-path, archives configured) +EASK_EMACS = $(EMACS_ENV) $(EASK) emacs --batch -q -L $(PROJECT_ROOT) -L $(TEST_DIR) .PHONY: help test test-all test-smoke test-unit test-integration test-file test-name \ - validate-parens validate compile lint install-deps \ + deps install-deps validate-parens validate compile lint \ + coverage coverage-clean \ clean clean-compiled clean-tests # Default target @@ -50,21 +65,25 @@ help: @echo "wttrin Makefile Targets:" @echo "" @echo " Testing:" - @echo " make test - Run all tests ($(words $(ALL_TESTS)) files: smoke → unit → integration)" + @echo " make test - Run all tests ($(words $(ALL_TESTS)) files: smoke -> unit -> integration)" @echo " make test-smoke - Run smoke tests only ($(words $(SMOKE_TESTS)) files)" @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 test-file FILE=<filename> - Run specific test file" @echo " make test-name TEST=<pattern> - 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:" @echo " make validate-parens - Check for unbalanced parentheses" @echo " make validate - Load wttrin.el to verify it compiles" - @echo " make compile - Byte-compile wttrin.el" + @echo " make compile - Byte-compile source files" @echo " make lint - Run all linters (checkdoc, package-lint, elisp-lint)" @echo "" @echo " Setup:" - @echo " make install-deps - Install required dependencies (xterm-color)" + @echo " make deps - Install dependencies via Eask (runtime + dev)" @echo "" @echo " Utilities:" @echo " make clean - Remove test artifacts and compiled files" @@ -77,19 +96,34 @@ help: @echo " make EMACS=emacs29 test # Use specific Emacs version" # ============================================================================ +# Setup Targets +# ============================================================================ + +deps: install-deps + +install-deps: + @if ! command -v $(EASK) >/dev/null 2>&1; then \ + echo "[x] Eask not found. Install from: https://emacs-eask.github.io/"; \ + exit 1; \ + fi + @echo "[i] Installing dependencies via Eask (runtime + dev)..." + @$(EMACS_ENV) $(EASK) install-deps --dev + @echo "[v] Dependencies installed" + +# ============================================================================ # Testing Targets # ============================================================================ test: test-all test-all: - @echo "Running all tests ($(words $(ALL_TESTS)) files: smoke → unit → integration)..." + @echo "Running all tests ($(words $(ALL_TESTS)) files: smoke -> unit -> integration)..." @$(MAKE) test-smoke @$(MAKE) test-unit @if [ $(words $(INTEGRATION_TESTS)) -gt 0 ]; then \ $(MAKE) test-integration; \ fi - @echo "[✓] All tests complete" + @echo "[v] All tests complete" test-smoke: @if [ $(words $(SMOKE_TESTS)) -eq 0 ]; then \ @@ -100,12 +134,12 @@ test-smoke: @failed=0; \ for test in $(SMOKE_TESTS); do \ echo " Testing $$test..."; \ - $(EMACS_TEST) -l ert -l $$test -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \ + $(EASK_EMACS) -l ert -l $$test -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \ done; \ if [ $$failed -eq 0 ]; then \ - echo "[✓] Smoke tests passed"; \ + echo "[v] Smoke tests passed"; \ else \ - echo "[✗] Smoke tests failed - package cannot load properly"; \ + echo "[x] Smoke tests failed - package cannot load properly"; \ exit 1; \ fi @@ -114,12 +148,12 @@ test-unit: @failed=0; \ for test in $(UNIT_TESTS); do \ echo " Testing $$test..."; \ - $(EMACS_TEST) -l ert -l $$test -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \ + $(EASK_EMACS) -l ert -l $$test -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \ done; \ if [ $$failed -eq 0 ]; then \ - echo "[✓] All unit tests passed"; \ + echo "[v] All unit tests passed"; \ else \ - echo "[✗] $$failed unit test file(s) failed"; \ + echo "[x] $$failed unit test file(s) failed"; \ exit 1; \ fi @@ -132,12 +166,12 @@ test-integration: @failed=0; \ for test in $(INTEGRATION_TESTS); do \ echo " Testing $$test..."; \ - $(EMACS_TEST) -l ert -l $$test -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \ + $(EASK_EMACS) -l ert -l $$test -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \ done; \ if [ $$failed -eq 0 ]; then \ - echo "[✓] All integration tests passed"; \ + echo "[v] All integration tests passed"; \ else \ - echo "[✗] $$failed integration test file(s) failed"; \ + echo "[x] $$failed integration test file(s) failed"; \ exit 1; \ fi @@ -148,8 +182,8 @@ ifndef FILE @exit 1 endif @echo "Running tests in $(FILE)..." - @$(EMACS_TEST) -l ert -l $(TEST_DIR)/$(FILE) -f ert-run-tests-batch-and-exit - @echo "[✓] Tests in $(FILE) complete" + @$(EASK_EMACS) -l ert -l $(TEST_DIR)/$(FILE) -f ert-run-tests-batch-and-exit + @echo "[v] Tests in $(FILE) complete" test-name: ifndef TEST @@ -158,11 +192,51 @@ ifndef TEST @exit 1 endif @echo "Running tests matching pattern: $(TEST)..." - @$(EMACS_TEST) \ + @$(EASK_EMACS) \ -l ert \ $(foreach test,$(ALL_TESTS),-l $(test)) \ --eval '(ert-run-tests-batch-and-exit "$(TEST)")' - @echo "[✓] Tests matching '$(TEST)' complete" + @echo "[v] Tests matching '$(TEST)' complete" + +# ============================================================================ +# Coverage +# ============================================================================ +# +# Each unit-test file runs in its own Emacs process (matching `make +# test-unit'); run-coverage-file.el instruments the source files before +# they're loaded, and undercover merges per-file results into a single +# simplecov JSON. + +coverage: coverage-clean $(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..."; \ + $(EASK_EMACS) \ + -l ert \ + -l $(TEST_DIR)/run-coverage-file.el \ + -l $$test \ + -f ert-run-tests-batch-and-exit || 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 "[v] 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 Targets @@ -170,25 +244,23 @@ endif validate-parens: @echo "Checking for unbalanced parentheses..." - @echo " Checking $(MAIN_FILE)..."; \ - $(EMACS_BATCH) --eval "(condition-case err \ - (progn \ - (find-file \"$(MAIN_FILE)\") \ - (check-parens) \ - (kill-emacs 0)) \ - (error (progn \ - (message \"ERROR: %s\" err) \ - (kill-emacs 1))))" 2>&1 > /dev/null && \ - echo "[✓] $(MAIN_FILE) has balanced parentheses" || \ - (echo "[✗] $(MAIN_FILE) has unbalanced parentheses" && exit 1) + @for src in $(SOURCE_FILES); do \ + echo " Checking $$src..."; \ + $(EMACS_BATCH) --eval "(condition-case err \ + (progn \ + (find-file \"$$src\") \ + (check-parens) \ + (kill-emacs 0)) \ + (error (progn \ + (message \"ERROR: %s\" err) \ + (kill-emacs 1))))" 2>&1 > /dev/null && \ + echo " [v] $$src has balanced parentheses" || \ + { echo " [x] $$src has unbalanced parentheses"; exit 1; }; \ + done validate: - @echo "Loading wttrin.el to verify compilation..." - @$(EMACS_BATCH) \ - --eval "(require 'package)" \ - --eval "(add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)" \ - --eval "(package-initialize)" \ - -L $(PROJECT_ROOT) \ + @echo "Loading $(MAIN_FILE) to verify compilation..." + @$(EASK_EMACS) \ --eval "(condition-case err \ (progn \ (load-file \"$(MAIN_FILE)\") \ @@ -196,66 +268,49 @@ validate: (error (progn \ (message \"ERROR loading %s: %s\" \"$(MAIN_FILE)\" err) \ (kill-emacs 1))))" && \ - echo "[✓] $(MAIN_FILE) loaded successfully" || \ - (echo "[✗] $(MAIN_FILE) failed to load" && exit 1) + echo "[v] $(MAIN_FILE) loaded successfully" || \ + (echo "[x] $(MAIN_FILE) failed to load" && exit 1) compile: - @echo "Byte-compiling wttrin.el..." - @$(EMACS_BATCH) \ - --eval "(require 'package)" \ - --eval "(add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)" \ - --eval "(package-initialize)" \ - -L $(PROJECT_ROOT) \ + @echo "Byte-compiling source files..." + @$(EASK_EMACS) \ --eval "(progn \ (setq byte-compile-error-on-warn nil) \ - (batch-byte-compile))" $(MAIN_FILE) - @echo "[✓] Compilation complete" + (batch-byte-compile))" $(SOURCE_FILES) + @echo "[v] Compilation complete" lint: - @echo "Running linters on wttrin.el..." - @echo "Note: checkdoc, package-lint, and elisp-lint must be installed" - @$(EMACS_BATCH) -L $(PROJECT_ROOT) \ + @echo "Running linters on $(MAIN_FILE)..." + @$(EASK_EMACS) \ --eval "(progn \ (require 'checkdoc nil t) \ (require 'package-lint nil t) \ (require 'elisp-lint nil t) \ (find-file \"$(MAIN_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) \ - (elisp-lint-file \"$(MAIN_FILE)\")))" && \ - echo "[✓] All linting checks passed" || \ - echo "[!] Linting issues found" - -# ============================================================================ -# Setup Targets -# ============================================================================ - -install-deps: - @echo "Installing dependencies..." - @$(EMACS) --batch \ - --eval "(require 'package)" \ - --eval "(add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)" \ - --eval "(package-initialize)" \ - --eval "(unless package-archive-contents (package-refresh-contents))" \ - --eval "(package-install 'xterm-color)" - @echo "[✓] Dependencies installed" + (message \"-- elisp-lint --\") \ + (elisp-lint-file \"$(MAIN_FILE)\")))" || true + @echo "[i] Lint complete (informational)" # ============================================================================ # Utility Targets # ============================================================================ clean: clean-tests clean-compiled - @echo "[✓] Clean complete" + @echo "[v] Clean complete" clean-compiled: @echo "Removing compiled files (.elc, .eln)..." @find $(PROJECT_ROOT) -type f \( -name "*.eln" -o -name "*.elc" \) -delete - @echo "[✓] Compiled files removed" + @echo "[v] Compiled files removed" clean-tests: @echo "Removing test artifacts..." @rm -rf $(HOME)/.temp-emacs-tests - @echo "[✓] Test artifacts removed" + @echo "[v] Test artifacts removed" diff --git a/tests/run-coverage-file.el b/tests/run-coverage-file.el new file mode 100644 index 0000000..347dc1f --- /dev/null +++ b/tests/run-coverage-file.el @@ -0,0 +1,34 @@ +;;; run-coverage-file.el --- Undercover setup for per-file coverage runs -*- lexical-binding: t; -*- + +;;; Commentary: +;; Loaded by `make coverage' before each unit-test file, BEFORE the +;; wttrin source files are loaded. Instrumenting must happen first so +;; the subsequent loads pick up the instrumented source. +;; +;; Coverage data is merged across per-file invocations into a single +;; simplecov JSON at .coverage/simplecov.json. + +;;; Code: + +(unless (require 'undercover nil t) + (message "") + (message "ERROR: undercover not installed.") + (message "Add `undercover' to Eask as a development dep, then run `make deps'.") + (message "") + (kill-emacs 1)) + +;; Force coverage collection in non-CI environments. Must be set after +;; loading undercover because the library's top-level form +;; `(setq undercover-force-coverage (getenv "UNDERCOVER_FORCE"))' would +;; otherwise overwrite the value. +(setq undercover-force-coverage t) + +(undercover "wttrin.el" + "wttrin-debug.el" + "wttrin-geolocation.el" + (:report-format 'simplecov) + (:report-file ".coverage/simplecov.json") + (:merge-report t) + (:send-report nil)) + +;;; run-coverage-file.el ends here |
