aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-05 04:58:34 -0500
committerCraig Jennings <c@cjennings.net>2026-05-05 04:58:34 -0500
commit6554cb8000a8f471f7c9e050e284f3fc364d5dad (patch)
treebdafa976a0899384e08e407465635eebfe454b77
parenta7e2eda6f36cb490a59f6c2670839ab1e52049fc (diff)
downloademacs-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--.gitignore4
-rw-r--r--Eask24
-rw-r--r--Makefile213
-rw-r--r--tests/run-coverage-file.el34
4 files changed, 196 insertions, 79 deletions
diff --git a/.gitignore b/.gitignore
index 6bc3206..b622a8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
/todo.org
/.ai/
/.stignore
+/.eask/
+/.coverage/
+/dist/
+*.elc
diff --git a/Eask b/Eask
new file mode 100644
index 0000000..eae1b17
--- /dev/null
+++ b/Eask
@@ -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"))
diff --git a/Makefile b/Makefile
index e8f5c1a..846fdb5 100644
--- a/Makefile
+++ b/Makefile
@@ -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