-
Notifications
You must be signed in to change notification settings - Fork 1
/
font-lock-regression-suite.el
421 lines (363 loc) · 14.9 KB
/
font-lock-regression-suite.el
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
;;; font-lock-regression-suite.el --- Test suite for font-lock.
;; Copyright (C) 2015-2017 Anders Lindgren
;; Author: Anders Lindgren
;; Keywords: faces
;; Version: 0.0.1
;; URL: https://github.com/Lindydancer/font-lock-regression-suite
;; Package-Requires: ((faceup "0.0.4"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; A collection of example source files for a large number of
;; programming languages, with ERT tests to ensure that syntax
;; highlighting does not accidentally change.
;;
;; For each source file, font-lock reference files are provided for
;; various Emacs versions. The reference files contains a plain-text
;; representation of source file with syntax highlighting, using the
;; format "faceup".
;;
;; Of course, the collection source file can be used for other kinds
;; of testing, not limited to font-lock regression testing.
;; Copyright note:
;;
;; The Copyright at the beginning of this file applies to the files
;; that drive the regression suite. It does not apply to the source
;; examples. See the individual source files for information
;; regarding copyright and licensing terms.
;; Usage:
;;
;; Run `M-x font-lock-regression-suite-add-testcases RET'. This will
;; add a number of ERT test cases to verify that source files are
;; highlighted according to the reference files.
;;
;; Run, for example, `M-x ert RET t RET' to run all tests.
;;
;; You can bind `font-lock-regression-suite-reference-version' to
;; another Emacs version, to see what the changes are compared to that
;; version.
;;
;; Reference files for several major Emacs versions are provided.
;; You can compare the files to see how syntax highlighting has
;; evolved over the years. If you find the "faceup" format hard to
;; read, you can run `M-x faceup-render-view-buffer RET' to see how
;; Emacs used to highlight the buffer (given that all relevant faces
;; are defined).
;; See also:
;;
;; - [Comments on syntax highlighting support provided by various
;; major modes](doc/CommentsOnMajorModes.org)
;;
;; - [The origin of the packages used as test
;; examples](doc/PackageSources.org)
;; Using the source files in other contexts:
;;
;; The function `font-lock-regression-suite-each-src-ref-file' can be
;; used to traverse all the files in the suite. It will accept one
;; argument, a function that will be called with four arguments: A
;; name, the source file name, the reference file name, and a mode.
;;
;; Today the mode is a single symbol. However, to be future
;; compatible, this can be a list of symbols, which should be called
;; in order. (Think of this as a major mode and a number of minor
;; modes.)
;;
;; Example:
;;
;; The following piece of code will traverse all source file and echo
;; the source names:
;;
;; (font-lock-regression-suite-each-src-ref-file
;; (lambda (name src-file ref-file mode)
;; (message src-file)))
;;
;; Real-world examples:
;;
;; This package is used to test the packages `font-lock-profiler' and
;; `font-lock-studio', to ensure that they behaves like the normal
;; font-lock engine, for non-trivial examples.
;; Other Font Lock Tools:
;;
;; This package is part of a suite of font-lock tools. The other
;; tools in the suite are:
;;
;;
;; Font Lock Studio:
;;
;; Interactive debugger for font-lock keywords (Emacs syntax
;; highlighting rules).
;;
;; Font Lock Studio lets you *single-step* Font Lock keywords --
;; matchers, highlights, and anchored rules, so that you can see what
;; happens when a buffer is fontified. You can set *breakpoints* on or
;; inside rules and *run* until one has been hit. When inside a rule,
;; matches are *visualized* using a palette of background colors. The
;; *explainer* can describe a rule in plain-text English. Tight
;; integration with *Edebug* allows you to step into Lisp expressions
;; that are part of the Font Lock keywords.
;;
;;
;; Font Lock Profiler:
;;
;; A profiler for font-lock keywords. This package measures time and
;; counts the number of times each part of a font-lock keyword is
;; used. For matchers, it counts the total number and the number of
;; successful matches.
;;
;; The result is presented in table that can be sorted by count or
;; time. The table can be expanded to include each part of the
;; font-lock keyword.
;;
;; In addition, this package can generate a log of all font-lock
;; events. This can be used to verify font-lock implementations,
;; concretely, this is used for back-to-back tests of the real
;; font-lock engine and Font Lock Studio, an interactive debugger for
;; font-lock keywords.
;;
;;
;; Highlight Refontification:
;;
;; Minor mode that visualizes how font-lock refontifies a buffer.
;; This is useful when developing or debugging font-lock keywords,
;; especially for keywords that span multiple lines.
;;
;; The background of the buffer is painted in a rainbow of colors,
;; where each band in the rainbow represent a region of the buffer
;; that has been refontified. When the buffer is modified, the
;; rainbow is updated.
;;
;;
;; Faceup:
;;
;; Emacs is capable of highlighting buffers based on language-specific
;; `font-lock' rules. This package makes it possible to perform
;; regression test for packages that provide font-lock rules.
;;
;; The underlying idea is to convert text with highlights ("faces")
;; into a plain text representation using the Faceup markup
;; language. This language is semi-human readable, for example:
;;
;; «k:this» is a keyword
;;
;; By comparing the current highlight with a highlight performed with
;; stable versions of a package, it's possible to automatically find
;; problems that otherwise would have been hard to spot.
;;
;; This package is designed to be used in conjunction with Ert, the
;; standard Emacs regression test system.
;;
;; The Faceup markup language is a generic markup language, regression
;; testing is merely one way to use it.
;;; Code:
;; TODO:
;;
;; * Rake script for regenerating all faceup files.
;;
;; * List orphaned reference files.
(require 'faceup)
(defvar font-lock-regression-suite-languages
'(("ada" ada-mode)
("autoconf" autoconf-mode)
("bat" bat-mode)
("C" c-mode ("CWarn" c-mode cwarn-mode))
("elisp" emacs-lisp-mode)
("f90" f90-mode)
("JavaScript" js-mode)
("m4" m4-mode ("ac" autoconf-mode))
("make" makefile-mode)
("Objective-C" objc-mode)
("Perl" perl-mode ("CPerl" cperl-mode))
("PostScript" ps-mode)
("prolog" prolog-mode)
("Python" python-mode)
("Ruby" ruby-mode))
"List of directories and corresponding modes.
Each entry in the list has the following format:
(DIR MODE-OR-MODES ...)
Where DIR is a directory in the source tree and MODE-OR-MODES is
a mode or a list of modes that should be used.")
(defvar font-lock-regression-suite-dir (faceup-this-file-directory))
(defvar font-lock-regression-suite-reference-version
(if (string-match "\\([0-9]+\\.[0-9]+\\.[0-9]+\\)\\.[0-9]+"
emacs-version)
(concat (match-string 1 emacs-version) ".x")
emacs-version)
"The version of the revision files to use, defaults to Emacs version.
For released Emacs versions, the same as `emacs-versions', else simplified.
When building an Emacs from source, the fourth version number is
increased for every build. This normalized this by replacing the
fourth version number with an `x'.")
;; -------------------------------------------------------------------
;; Support functions.
;;
(defun font-lock-regression-suite-each-src-ref-file--internal
(src-dir ref-dir ref-start path func &rest args)
"Helper function for `font-lock-regression-suite-each-src-ref-file'.
Traverse SRC-DIR recursively, relative to PATH and call FUNC with
name, source file, reference files (rooted in REF-DIR), and
ARGS."
(dolist (f (directory-files (if path
(concat src-dir path)
src-dir)))
(unless (string-match "^\\." f) ; ".", "..", ".nosearch" etc.
(let ((new-path (if path
(concat path "/" f)
f)))
(if (file-directory-p (concat src-dir new-path))
(apply 'font-lock-regression-suite-each-src-ref-file--internal
src-dir ref-dir ref-start new-path func args)
(apply func
;; Note: In theory, the same name could be generated
;; twice. For example, if both "A-B" and "A/B"
;; exists, they will both be mapped to "A-B". One way
;; to work-around this would be to map "-" into "--".
(font-lock-regression-suite-dashify
(concat ref-start "/" new-path))
(concat src-dir new-path)
(concat ref-dir ref-start "/" new-path ".faceup")
args))))))
(defun font-lock-regression-suite-each-src-ref-file (func &rest args)
"Call FUNC with each name, source file, reference file, mode, and ARGS.
The name is a unique identifier representing the file. The
reference file may not exist.
Modes is a function to call or a list of function to call. You
can use `font-lock-regression-suite-apply-modes' to enable the
modes.
`font-lock-regression-suite-dir' contains the root of the source
files and `font-lock-regression-suite-languages' contains a list
of subdirectories and corresponding modes.
When non-nil `font-lock-regression-suite-reference-version', the
reference files of that version of Emacs is used. When nil, the
reference files of the current Emacs version is used.
Example:
(font-lock-regression-suite-each-src-ref-file
(lambda (name src-file ref-file modes)
(message \"%s: %s %s in %s\" name src-file ref-file mode)))"
(let ((seen-ids '()))
(dolist (entry font-lock-regression-suite-languages)
(dolist (mode-or-modes (cdr entry))
(let* ((ref-dir (font-lock-regression-suite-reference-directory))
(src-start (nth 0 entry))
(ref-start-base (if (stringp (car-safe mode-or-modes))
(pop mode-or-modes)
(nth 0 entry)))
(ref-start ref-start-base)
(count 2))
;; Make the reference directory unique.
(while (member ref-start seen-ids)
(setq ref-start (format "%s-%d" ref-start-base count))
(setq count (+ count 1)))
(push ref-start seen-ids)
(apply
#'font-lock-regression-suite-each-src-ref-file--internal
(concat font-lock-regression-suite-dir
"src/"
src-start "/")
ref-dir
ref-start
nil
func
mode-or-modes
args))))))
(defun font-lock-regression-suite-dashify (path)
"Convert PATH to something suitable to be part of an elisp identifier."
(setq path (file-name-sans-extension path))
(while (string-match "/" path)
(setq path (replace-match "-" nil nil path)))
path)
(defun font-lock-regression-suite-reference-directory ()
"The root of the reference directory, with a trailing slash."
(concat font-lock-regression-suite-dir
"ref/"
font-lock-regression-suite-reference-version "/"))
(defun font-lock-regression-suite-apply-modes (modes)
"Apply all modes in MODES.
Modes can be a function to call or a list of functions.
Return nil if any of the function isn't defined, non-nil otherwise."
;; Note: Both are needed to recognize lambda expressions and symbols
;; referring to undefined functions.
(when (or (symbolp modes)
(functionp modes))
(setq modes (list modes)))
(let ((res t))
(while (and res
modes)
(let ((m (pop modes)))
(if (functionp m)
(funcall m)
(setq res nil))))
res))
;; ----------------------------------------------------------------------
;; Add ERT test cases.
;;
;; This generates one ERT test case for each source file. This allows
;; you to use the ERT selection mechanism to test a subset of the files.
(defun font-lock-regression-suite-add-testcases ()
(interactive)
(font-lock-regression-suite-each-src-ref-file
(lambda (name
src-file
ref-file
mode)
(eval `(ert-deftest
,(intern (concat "font-lock-regression-suite--" name))
()
(if (file-exists-p ,ref-file)
(should (faceup-test-font-lock-file
(quote ,mode)
,src-file
,ref-file))
(error "The reference file `%s' does not exist."
,ref-file)))))))
;; ----------------------------------------------------------------------
;; Regenerate
;;
(defun font-lock-regression-suite-regenerate (&optional force)
"Regenerate all reference files.
When C-u prefix, or when FORCE is non-nil, only regenerate missing files."
(interactive (list current-prefix-arg))
(font-lock-regression-suite-each-src-ref-file
(lambda (name
src-file
ref-file
modes)
(when (or force
(not (file-exists-p ref-file)))
(with-temp-buffer
(insert-file-contents src-file)
(when (font-lock-regression-suite-apply-modes modes)
;; Don't generate a reference file when the font-lock
;; keywords have triggered an error. (For example,
;; prolog-mode on Emacs 23.3 throws a "No match 3 in
;; highlight" error.)
(when (condition-case nil
(progn
(font-lock-fontify-region (point-min) (point-max))
t)
(error nil))
(make-directory (file-name-directory ref-file) t)
(faceup-write-file ref-file))))))))
;; ----------------------------------------------------------------------
;; Testing
;;
(defun font-lock-regression-suite-list ()
"Echo all source files in the regression suite."
(interactive)
(with-output-to-temp-buffer "*FontLockRegressionSuite*"
(font-lock-regression-suite-each-src-ref-file
(lambda (name src-file ref-file mode)
(princ (format "%s:\n %s\n %s\n %s\n" name src-file ref-file mode))))
(display-buffer (current-buffer))))
;; ----------------------------------------------------------------------
;; The End
;;
(provide 'font-lock-regression-suite)
;;; font-lock-regression-suite.el ends here