1 ;;; context-coloring-test.el --- Tests for context coloring -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
5 ;; This file is part of GNU Emacs.
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, either version 3 of the License, or
10 ;; (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ;; Tests for context coloring.
24 ;; Use with `make test'.
29 (require 'context-coloring)
34 ;;; Test running utilities
36 (defconst context-coloring-test-path
37 (file-name-directory (or load-file-name buffer-file-name))
38 "This file's directory.")
40 (defun context-coloring-test-read-file (path)
41 "Return the file's contents from PATH as a string."
43 (insert-file-contents (expand-file-name path context-coloring-test-path))
46 (defmacro context-coloring-test-with-fixture (fixture &rest body)
47 "With the relative FIXTURE, evaluate BODY in a temporary
51 (insert (context-coloring-test-read-file ,fixture))
55 ;;; Test defining utilities
57 (cl-defmacro context-coloring-test-define-deftest (name
61 &key enable-context-coloring-mode
64 "Define a deftest defmacro for tests prefixed with NAME. MODE
65 is called to set up tests' environments. EXTENSION denotes the
66 suffix for tests' fixture files. If NO-FIXTURE is non-nil, don't
67 use a fixture. If ENABLE-CONTEXT-COLORING-MODE is non-nil,
68 `context-coloring-mode' is activated before tests. Functions
69 BEFORE-EACH and AFTER-EACH run before the major mode is activated
70 before each test, and after each test, even if an error is
72 (declare (indent defun))
73 (let ((macro-name (intern (format "context-coloring-test-deftest%s"
75 ;; No name means no dash.
77 (t (format "-%s" name)))))))
78 `(cl-defmacro ,macro-name (name
83 (declare (indent defun))
84 ;; Commas in nested backquotes are not evaluated. Binding the variables
85 ;; here is probably the cleanest workaround.
87 (before-each ',before-each)
88 (after-each ',after-each)
89 (test-name (intern (format ,(format "%s-%%s"
92 (t "generic"))) name)))
94 (fixture (format "./fixtures/%s" fixture))
95 (,no-fixture "./fixtures/empty")
96 (t (format ,(format "./fixtures/%%s.%s" extension) name)))))
97 ,@`((let ((enable-context-coloring-mode ,enable-context-coloring-mode))
98 `(ert-deftest ,test-name ()
99 (context-coloring-test-with-fixture
101 (when ,before-each (funcall ,before-each))
103 (when ,before (funcall ,before))
104 (when ,enable-context-coloring-mode (context-coloring-mode))
108 (when ,after (funcall ,after))
109 (when ,after-each (funcall ,after-each)))))))))))
111 (context-coloring-test-define-deftest nil
112 :mode #'fundamental-mode
115 (context-coloring-test-define-deftest javascript
118 :enable-context-coloring-mode t
119 :before-each (lambda ()
120 (setq js2-mode-show-parse-errors nil)
121 (setq js2-mode-show-strict-warnings nil)))
123 (context-coloring-test-define-deftest emacs-lisp
124 :mode #'emacs-lisp-mode
126 :enable-context-coloring-mode t)
128 (context-coloring-test-define-deftest eval-expression
129 :mode #'fundamental-mode
133 ;;; Assertion functions
135 (defun context-coloring-test-get-last-message ()
136 "Get the last message in the current messages bufffer."
137 (let ((messages (split-string
138 (buffer-substring-no-properties
142 (car (nthcdr (- (length messages) 2) messages))))
144 (defun context-coloring-test-assert-message (expected buffer)
145 "Assert that message EXPECTED is at the end of BUFFER."
146 (when (null (get-buffer buffer))
150 "Expected buffer `%s' to have message \"%s\", "
151 "but the buffer did not have any messages.")
153 (with-current-buffer buffer
154 (let ((message (context-coloring-test-get-last-message)))
155 (when (not (equal message expected))
159 "Expected buffer `%s' to have message \"%s\", "
160 "but instead it was \"%s\"")
164 (defun context-coloring-test-assert-not-message (expected buffer)
165 "Assert that message EXPECTED is not at the end of BUFFER."
166 (when (get-buffer buffer)
167 (with-current-buffer buffer
168 (let ((message (context-coloring-test-get-last-message)))
169 (when (equal message expected)
173 "Expected buffer `%s' not to have message \"%s\", "
175 buffer expected)))))))
177 (defun context-coloring-test-assert-error (body error-message)
178 "Assert that BODY signals ERROR-MESSAGE."
179 (let ((error-signaled-p nil))
184 (setq error-signaled-p t)
185 (when (not (string-equal (cadr err) error-message))
186 (ert-fail (format (concat "Expected the error \"%s\" to be thrown, "
187 "but instead it was \"%s\".")
190 (when (not error-signaled-p)
191 (ert-fail "Expected an error to be thrown, but there wasn't."))))
194 ;;; Miscellaneous tests
196 (defmacro context-coloring-test-define-derived-mode (name)
197 "Define a derived mode exclusively for any test with NAME."
198 (let ((name (intern (format "context-coloring-test-%s-mode" name))))
199 `(define-derived-mode ,name fundamental-mode "Testing")))
201 (defvar context-coloring-test-caused-p nil
202 "Dumb flag tracking for lambdas inside old advice definitions
203 which don't seem to have lexical binding.")
205 (defmacro context-coloring-test-assert-causes-coloring (&rest body)
206 "Assert that BODY causes coloring."
208 ;; Gross, but I want this to pass on 24.3.
209 (ad-add-advice #'context-coloring-colorize
210 '(assert-causes-coloring
213 (setq context-coloring-test-caused-p t))))
216 (ad-activate #'context-coloring-colorize)
218 (when (not context-coloring-test-caused-p)
219 (ert-fail "Expected to have colorized, but it didn't."))))
221 (defun context-coloring-test-cleanup-assert-causes-coloring ()
222 (ad-unadvise #'context-coloring-colorize)
223 (setq context-coloring-test-caused-p nil))
225 (context-coloring-test-define-derived-mode mode-startup)
227 (context-coloring-test-deftest mode-startup
229 (context-coloring-define-dispatch
231 :modes '(context-coloring-test-mode-startup-mode)
233 (context-coloring-test-mode-startup-mode)
234 (context-coloring-test-assert-causes-coloring
235 (context-coloring-mode)))
237 (context-coloring-test-cleanup-assert-causes-coloring)))
239 (context-coloring-test-define-derived-mode change-detection)
241 (context-coloring-test-deftest change-detection
243 (context-coloring-define-dispatch
245 :modes '(context-coloring-test-change-detection-mode)
247 :setup #'context-coloring-setup-idle-change-detection
248 :teardown #'context-coloring-teardown-idle-change-detection)
249 (context-coloring-test-change-detection-mode)
250 (context-coloring-mode)
251 (context-coloring-test-assert-causes-coloring
253 ;; Simply cannot figure out how to trigger an idle timer; would much rather
254 ;; test that. But (current-idle-time) always returns nil in these tests.
255 (context-coloring-maybe-colorize-with-buffer (current-buffer))))
257 (context-coloring-test-cleanup-assert-causes-coloring)))
259 (context-coloring-test-deftest unsupported-mode
261 (context-coloring-mode)
262 (context-coloring-test-assert-message
263 "Context coloring is not available for this major mode"
266 (context-coloring-test-deftest derived-mode
268 (lisp-interaction-mode)
269 (context-coloring-mode)
270 (context-coloring-test-assert-not-message
271 "Context coloring is not available for this major mode"
274 (context-coloring-test-define-derived-mode define-dispatch-error)
276 (context-coloring-test-deftest define-dispatch-error
278 (context-coloring-test-assert-error
280 (context-coloring-define-dispatch
281 'define-dispatch-no-modes))
282 "No mode or predicate defined for dispatch")
283 (context-coloring-test-assert-error
285 (context-coloring-define-dispatch
286 'define-dispatch-no-strategy
287 :modes '(context-coloring-test-define-dispatch-error-mode)))
288 "No colorizer defined for dispatch")))
290 (context-coloring-test-define-derived-mode disable-mode)
292 (context-coloring-test-deftest disable-mode
295 (context-coloring-define-dispatch
297 :modes '(context-coloring-test-disable-mode-mode)
301 (context-coloring-test-disable-mode-mode)
302 (context-coloring-mode)
303 (context-coloring-mode -1)
304 (when (not torn-down)
305 (ert-fail "Expected teardown function to have been called, but it wasn't.")))))
307 (defun context-coloring-test-assert-maximum-face (expected)
308 "Assert that `context-coloring-maximum-face' is EXPECTED."
309 (when (not (= context-coloring-maximum-face expected))
310 (ert-fail (format "Expected maximum face to be %s, but it was %s"
311 expected context-coloring-maximum-face))))
313 (deftheme context-coloring-test-custom-theme)
315 (context-coloring-test-define-derived-mode custom-theme)
317 (context-coloring-test-deftest custom-theme
319 (custom-theme-set-faces
320 'context-coloring-test-custom-theme
321 '(context-coloring-level-0-face ((t :foreground "#aaaaaa")))
322 '(context-coloring-level-1-face ((t :foreground "#bbbbbb"))))
324 '(context-coloring-level-0-face ((t :foreground "#aaaaaa"))))
325 (enable-theme 'context-coloring-test-custom-theme)
326 (context-coloring-define-dispatch
328 :modes '(context-coloring-test-custom-theme-mode)
330 (context-coloring-test-custom-theme-mode)
331 (context-coloring-colorize)
332 (context-coloring-test-assert-maximum-face 1)
333 ;; This theme should now be ignored in favor of the `user' theme.
334 (custom-theme-reset-faces
335 'context-coloring-test-custom-theme
336 '(context-coloring-level-0-face nil)
337 '(context-coloring-level-1-face nil))
338 (context-coloring-colorize)
339 ;; Maximum face for `user'.
340 (context-coloring-test-assert-maximum-face 0)
341 ;; Now `user' should be ignored too.
343 '(context-coloring-level-0-face nil))
344 (context-coloring-colorize)
345 ;; Expect the package's defaults.
346 (context-coloring-test-assert-maximum-face
347 context-coloring-default-maximum-face))
350 '(context-coloring-level-0-face nil))
351 (disable-theme 'context-coloring-test-custom-theme)))
356 (defun context-coloring-test-assert-position-level (position level)
357 "Assert that POSITION has LEVEL."
358 (let ((face (get-text-property position 'face))
361 (let* ((face-string (symbol-name face))
362 (matches (string-match
363 context-coloring-level-face-regexp
366 (setq actual-level (string-to-number
367 (substring face-string
370 (= level actual-level)))))
371 (ert-fail (format (concat "Expected level at position %s, "
372 "which is \"%s\", to be %s; "
375 (buffer-substring-no-properties position (1+ position)) level
378 (defun context-coloring-test-assert-position-face (position face-regexp)
379 "Assert that the face at POSITION satisfies FACE-REGEXP."
380 (let ((face (get-text-property position 'face)))
382 ;; Pass a non-string to do an `equal' check (against a symbol or nil).
383 (unless (stringp face-regexp)
384 (not (equal face-regexp face)))
385 ;; Otherwise do the matching.
386 (when (stringp face-regexp)
387 (not (string-match-p face-regexp (symbol-name face)))))
388 (ert-fail (format (concat "Expected face at position %s, "
389 "which is \"%s\", to be %s; "
392 (buffer-substring-no-properties position (1+ position)) face-regexp
395 (defun context-coloring-test-assert-position-comment (position)
396 "Assert that the face at POSITION is a comment."
397 (context-coloring-test-assert-position-face
398 position "\\`font-lock-comment\\(-delimiter\\)?-face\\'"))
400 (defun context-coloring-test-assert-position-constant-comment (position)
401 "Assert that the face at POSITION is a constant comment."
402 (context-coloring-test-assert-position-face position '(font-lock-constant-face
403 font-lock-comment-face)))
405 (defun context-coloring-test-assert-position-string (position)
406 "Assert that the face at POSITION is a string."
407 (context-coloring-test-assert-position-face position 'font-lock-string-face))
409 (defun context-coloring-test-assert-position-nil (position)
410 "Assert that the face at POSITION is nil."
411 (context-coloring-test-assert-position-face position nil))
413 (defun context-coloring-test-assert-coloring (map)
414 "Assert that the current buffer's coloring will match MAP.
416 MAP's newlines should correspond to the current fixture.
418 The following characters appearing in MAP assert coloring for
419 corresponding points in the fixture:
421 0-9: Level equals number.
422 C: Face is constant comment.
427 Any other characters are discarded. Characters \"x\" and any
428 other non-letters are guaranteed to always be discarded."
429 ;; Omit the superfluous, formatting-related leading newline. Can't use
430 ;; `save-excursion' here because if an assertion fails it will cause future
431 ;; tests to get messed up.
432 (goto-char (point-min))
433 (let* ((map (substring map 1))
437 (while (< index (length map))
438 (setq char-string (substring map index (1+ index)))
439 (setq char (string-to-char char-string))
448 (context-coloring-test-assert-position-level
449 (point) (string-to-number char-string))
451 ;; 'C' = Constant comment
453 (context-coloring-test-assert-position-constant-comment (point))
457 (context-coloring-test-assert-position-comment (point))
461 (context-coloring-test-assert-position-nil (point))
465 (context-coloring-test-assert-position-string (point))
469 (setq index (1+ index)))))
471 (context-coloring-test-deftest-javascript function-scopes
473 (context-coloring-test-assert-coloring "
474 000 0 0 11111111 11 110
476 111 1 1 22222222 22 221
480 (context-coloring-test-deftest-javascript global
482 (context-coloring-test-assert-coloring "
484 111 1 1 00000001xxx11
487 (context-coloring-test-deftest-javascript block-scopes
489 (context-coloring-test-assert-coloring "
497 (setq context-coloring-javascript-block-scopes t))
499 (setq context-coloring-javascript-block-scopes nil)))
501 (context-coloring-test-deftest-javascript catch
503 (context-coloring-test-assert-coloring "
513 (context-coloring-test-deftest-javascript key-names
515 (context-coloring-test-assert-coloring "
523 (context-coloring-test-deftest-javascript property-lookup
525 (context-coloring-test-assert-coloring "
532 (context-coloring-test-deftest-javascript key-values
534 (context-coloring-test-assert-coloring "
544 (context-coloring-test-deftest-javascript syntactic-comments-and-strings
546 (context-coloring-test-assert-coloring "
551 :fixture "comments-and-strings.js")
553 (context-coloring-test-deftest-javascript syntactic-comments
555 (context-coloring-test-assert-coloring "
560 :fixture "comments-and-strings.js"
562 (setq context-coloring-syntactic-strings nil))
564 (setq context-coloring-syntactic-strings t)))
566 (context-coloring-test-deftest-javascript syntactic-strings
568 (context-coloring-test-assert-coloring "
573 :fixture "comments-and-strings.js"
575 (setq context-coloring-syntactic-comments nil))
577 (setq context-coloring-syntactic-comments t)))
579 (context-coloring-test-deftest-javascript unterminated-comment
580 ;; As long as `add-text-properties' doesn't signal an error, this test passes.
583 (context-coloring-test-deftest-emacs-lisp defun
585 (context-coloring-test-assert-coloring "
586 111111 000 1111 111 111111111 1111
587 11 111 111 111 000011
595 (context-coloring-test-deftest-emacs-lisp defadvice
597 (context-coloring-test-assert-coloring "
598 1111111111 0 1111111 111111 11111 111 111111111
602 (context-coloring-test-deftest-emacs-lisp lambda
604 (context-coloring-test-assert-coloring "
605 00000000 1111111 1111
606 11111111 11 2222222 2222
607 222 22 12 2221 111 0 00")))
609 (context-coloring-test-deftest-emacs-lisp quote
611 (context-coloring-test-assert-coloring "
612 (xxxxx 0000000 00 00000)
613 (xxx () (xxxxxxxxx (,0000)))
618 111111 1 1111111111 11 111 1 111 1 00001 10000 11 00001 1 10000
626 (xxxxxx () 111111 11111)")))
628 (context-coloring-test-deftest-emacs-lisp splice
630 (context-coloring-test-assert-coloring "
632 111111 00001 100001)")))
634 (context-coloring-test-deftest-emacs-lisp comment
636 ;; Just check that the comment isn't parsed syntactically.
637 (context-coloring-test-assert-coloring "
639 (xx (x xxxxx-xxxx xx) cccccccccc
640 11 00000-0000 11))) cccccccccc")))
642 (context-coloring-test-deftest-emacs-lisp string
644 (context-coloring-test-assert-coloring "
646 (xxxxxx x x sss 1 0 sssss 0 1 sssssss11")))
648 (context-coloring-test-deftest-emacs-lisp ignored
650 (context-coloring-test-assert-coloring "
652 (x x 1 11 11 111 111 11 11 11 1 111 (1 1 1)))")))
654 (context-coloring-test-deftest-emacs-lisp sexp
656 (context-coloring-test-assert-coloring "
662 (context-coloring-test-deftest-emacs-lisp let
664 (context-coloring-test-assert-coloring "
677 (context-coloring-test-deftest-emacs-lisp let*
679 (context-coloring-test-assert-coloring "
689 2222 1 1 2 2 2 000022
690 1111 1 1 1 0 0 000011")))
692 (context-coloring-test-deftest-emacs-lisp cond
694 (context-coloring-test-assert-coloring "
704 (context-coloring-test-deftest-emacs-lisp condition-case
706 (context-coloring-test-assert-coloring "
710 1111111 111111 111 000011
712 (111111111-1111-111111-11111 111
718 (context-coloring-test-deftest-emacs-lisp dolist
720 (context-coloring-test-assert-coloring "
722 2222222 2222 1111 2222222
723 3333333 33 33 222 1111 2222223321")))
725 (defun context-coloring-test-insert-unread-space ()
726 "Simulate the insertion of a space as if by a user."
727 (setq unread-command-events (cons '(t . 32)
728 unread-command-events)))
730 (defun context-coloring-test-remove-faces ()
731 "Remove all faces in the current buffer."
732 (remove-text-properties (point-min) (point-max) '(face nil)))
734 (context-coloring-test-deftest-emacs-lisp iteration
736 (let ((context-coloring-elisp-sexps-per-pause 2))
737 (context-coloring-colorize)
738 (context-coloring-test-assert-coloring "
741 (context-coloring-test-remove-faces)
742 (context-coloring-test-insert-unread-space)
743 (context-coloring-colorize)
744 ;; Coloring is interrupted after the first "sexp" (the comment in this
746 (context-coloring-test-assert-coloring "
750 (context-coloring-test-deftest-emacs-lisp changed
752 (context-coloring-test-remove-faces)
754 (goto-char (point-min))
755 (forward-line (1- 3))
757 ;; Mock `pos-visible-in-window-p' because in batch mode `get-buffer-window'
758 ;; returns nil. Emacs must not have a window in that environment.
759 (cl-letf (((symbol-function 'pos-visible-in-window-p)
763 ;; First and third calls start from center. Second and
764 ;; fourth calls are made immediately after moving past
765 ;; the first defun in either direction "off screen".
771 (setq calls (1+ calls)))))))
772 (context-coloring-colorize))
773 (context-coloring-test-assert-coloring "
778 nnnnn n nnn nnnnnnnn")))
780 (context-coloring-test-deftest-emacs-lisp unbalanced-parenthesis
782 (context-coloring-test-assert-coloring "
786 (context-coloring-test-deftest-eval-expression let
788 (minibuffer-with-setup-hook
790 ;; Perform the test in a hook as it's the only way I know of examining
791 ;; the minibuffer's contents. The contents are implicitly submitted,
792 ;; so we have to ignore the errors in the arbitrary test subject code.
793 (insert "(ignore-errors (let (a) (message a free)))")
794 (context-coloring-colorize)
795 (context-coloring-test-assert-coloring "
796 xxxx: 0000000-000000 1111 111 11111111 1 0000110"))
797 ;; Simulate user input because `call-interactively' is blocking and
798 ;; doesn't seem to run the hook.
801 [?\C-u] ;; Don't output the result of the arbitrary test subject code.
804 (provide 'context-coloring-test)
806 ;;; context-coloring-test.el ends here