aboutsummaryrefslogtreecommitdiff
path: root/TESTING.org
blob: d2c320529848b4bc61c370f4bc6562b6372ef4cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#+TITLE: Wttrin Test Suite
#+AUTHOR: Craig Jennings
#+DATE: 2026-04-04

Quick reference for running and writing tests in the wttrin project.

[[Quick start]] | [[Running tests]] | [[Writing tests]] | [[Test infrastructure]] | [[Key patterns]] | [[Important notes]] | [[Dependencies]] | [[Test inventory]]

* Quick start

#+begin_src sh
# From the project root:
make test                              # Run all tests (smoke -> unit -> integration)
make test-unit                         # Run unit tests only
make test-integration                  # Run integration tests only

# More options:
make test-file FILE=test-wttrin--build-url.el   # Run specific test file
make test-name TEST=test-wttrin--build-url-*    # Run tests matching pattern
#+end_src

* Running tests

All test logic lives in the root =Makefile=. Each test file runs in its own Emacs batch process for isolation.

| Command | Purpose |
|---------+---------|
| =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 (=test-integration-*.el=) |
| =make test-file FILE=test-foo.el= | Run a specific test file |
| =make test-name TEST=pattern= | Run tests matching an ERT name pattern |
| =make validate-parens= | Check for unbalanced parentheses |
| =make validate= | Load wttrin.el to verify it compiles |
| =make compile= | Byte-compile wttrin.el |
| =make lint= | Run all linters (checkdoc, package-lint, elisp-lint) |
| =make clean= | Remove test artifacts and compiled files |
| =make help= | Show all available commands |

Dependencies are auto-detected from =~/.emacs.d/elpa/=. Override the Emacs binary with the =EMACS= environment variable.

** Examples

#+begin_src sh
# Run one file
make test-file FILE=test-wttrin--build-url.el

# Run tests matching a pattern
make test-name TEST="test-wttrin--build-url-*"

# Use a specific Emacs version
make EMACS=emacs29 test
#+end_src

* Writing tests

** File structure

Every test file requires =ert=, =wttrin=, and =testutil-wttrin=:

#+begin_src elisp
;;; test-wttrin--FEATURE.el --- Tests for FEATURE -*- lexical-binding: t; -*-

;; Copyright (C) 2025-2026 Craig Jennings
;; Author: Craig Jennings <c@cjennings.net>
;; License: GPL-3.0-or-later

;;; Commentary:
;; Unit tests for FEATURE.

;;; Code:

(require 'ert)
(require 'wttrin)
(require 'testutil-wttrin)

;;; Setup and Teardown

(defun test-wttrin--FEATURE-setup ()
  "Setup for FEATURE tests."
  (testutil-wttrin-setup))

(defun test-wttrin--FEATURE-teardown ()
  "Teardown for FEATURE tests."
  (testutil-wttrin-teardown))

;;; Normal Cases

(ert-deftest test-wttrin--FEATURE-normal-description ()
  "Descriptive docstring."
  (test-wttrin--FEATURE-setup)
  (unwind-protect
      (should (equal expected (function-under-test input)))
    (test-wttrin--FEATURE-teardown)))

;;; Boundary Cases
;;; Error Cases

(provide 'test-wttrin--FEATURE)
;;; test-wttrin--FEATURE.el ends here
#+end_src

** Naming convention

#+begin_example
test-wttrin--FUNCTION-CATEGORY-description
              |         |
              |         +-- normal, boundary, error
              +------------ function or module name
#+end_example

Examples:
- =test-wttrin--build-url-normal-simple-city-returns-correct-url=
- =test-wttrin--make-cache-key-boundary-empty-location=
- =test-wttrin--validate-weather-data-error-nil-response=

** Test categories

Tests are split into three categories:

- *Normal*: Standard inputs, expected use cases, common workflows
- *Boundary*: Empty inputs, nil values, single-element results, unicode, max values
- *Error*: Invalid inputs, malformed data, network failures, missing parameters

* Test infrastructure

** testutil-wttrin.el

Shared utilities loaded by all test files. Provides fixtures, cache helpers, mocking macros, and setup/teardown functions.

*** Fixtures

| Constant | Content |
|----------+---------|
| =testutil-wttrin-sample-weather-response= | Parsed weather output (Paris, no ANSI) |
| =testutil-wttrin-sample-error-response= | wttr.in error string |
| =testutil-wttrin-sample-ansi-response= | Weather with ANSI color codes |
| =testutil-wttrin-sample-full-weather= | Full weather display (Berkeley) |

Additional fixture files live in =tests/fixtures/=.

*** Cache helpers

| Function | Purpose |
|----------+---------|
| =testutil-wttrin-clear-cache= | Clear the wttrin cache hash table |
| =testutil-wttrin-add-to-cache LOCATION DATA &optional AGE-SECONDS= | Insert data into cache, optionally aged |
| =testutil-wttrin-cache-size= | Return number of cache entries |
| =testutil-wttrin-set-mode-line-cache DATA &optional AGE-SECONDS= | Set mode-line cache with optional age |

*** Config override macros

#+begin_src elisp
;; Override unit system for a test
(testutil-wttrin-with-unit-system "m"
  (should (string-match "\\?m" (wttrin--build-url "Paris"))))

;; Override refresh interval
(testutil-wttrin-with-refresh-interval 60
  (should (= wttrin-refresh-interval 60)))

;; Override max cache entries
(testutil-wttrin-with-cache-max 5
  (should (= wttrin-cache-max-entries 5)))
#+end_src

*** Buffer management

#+begin_src elisp
;; Clean *wttr.in* buffer with auto-cleanup
(testutil-wttrin-with-clean-weather-buffer
  (wttrin--display-weather "Paris" weather-data)
  (should (get-buffer "*wttr.in*")))
#+end_src

*** HTTP mocking

#+begin_src elisp
;; Mock url-retrieve to return a canned response
(testutil-wttrin-mock-http-response "Weather report: Paris\n..."
  (wttrin--fetch-url "https://wttr.in/Paris"
    (lambda (data &optional error-info)
      (should (stringp data)))))
#+end_src

*** Setup and teardown

#+begin_src elisp
(testutil-wttrin-setup)    ; clears cache, resets wttrin--force-refresh
(testutil-wttrin-teardown) ; same cleanup for test end
#+end_src

* Key patterns

** Mocking external dependencies

#+begin_src elisp
(cl-letf (((symbol-function 'url-retrieve)
           (lambda (url callback)
             (with-temp-buffer
               (insert "HTTP/1.1 200 OK\n\n" response-body)
               (funcall callback nil)))))
  ;; url-retrieve is mocked only within this scope
  )
#+end_src

** Mocking time

#+begin_src elisp
(cl-letf (((symbol-function 'float-time) (lambda () 1000.0)))
  ;; float-time returns a fixed value for deterministic age computation
  )
#+end_src

** Overriding config

#+begin_src elisp
(let ((wttrin-unit-system "m")
      (wttrin-default-locations '("Paris" "London")))
  ;; config is scoped to this let block
  )
#+end_src

* Important notes

1. *Load path*: The Makefile auto-adds project root and =tests/= to the load path, and initializes MELPA packages
2. *Test isolation*: Each test file runs in its own Emacs process -- no cross-file state leakage
3. *Cache cleanup*: Always call =testutil-wttrin-setup= / =testutil-wttrin-teardown= to avoid cache bleed between tests
4. *Mock warnings*: "Redefining 'url-retrieve' might break native compilation" is normal and expected
5. *ANSI codes*: Use =testutil-wttrin-sample-ansi-response= when testing color rendering; use plain fixtures for logic tests
6. *Debug tests*: Set =wttrin-debug= to non-nil before requiring =wttrin-debug= in debug test files

* Dependencies

Required packages (auto-detected by Makefile):
- =xterm-color= (ANSI color rendering)

Use =make install-deps= to install. Override the ELPA directory with the =ELPA_DIR= environment variable.

* Test inventory

334 tests across 38 files (as of 2026-04-04).

Top files by count:
| File | Tests |
|------+-------|
| test-wttrin--mode-line-update-display.el | 36 |
| test-wttrin--handle-fetch-callback.el | 16 |
| test-wttrin--extract-response-body.el | 16 |
| test-wttrin-smoke.el | 15 |
| test-wttrin--validate-weather-data.el | 13 |
| test-wttrin--display-weather.el | 13 |
| test-wttrin--process-weather-content.el | 12 |
| test-wttrin--cleanup-cache-if-needed.el | 12 |
| test-wttrin--build-url.el | 12 |
| test-wttrin--make-cache-key.el | 11 |
| test-wttrin-error-propagation.el | 11 |
| test-wttrin--mode-line-tooltip.el | 10 |
| test-wttrin--mode-line-helpers.el | 10 |
| test-wttrin--get-cached-or-fetch.el | 10 |
| test-wttrin-ansi-color-rendering.el | 10 |
| test-wttrin--add-buffer-instructions.el | 10 |

Run =make test= from the project root for the full suite.