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 relative FIXTURE, evaluate BODY in a temporary buffer."
50 (insert (context-coloring-test-read-file ,fixture))
54 ;;; Test defining utilities
56 (cl-defmacro context-coloring-test-define-deftest (name
60 &key enable-context-coloring-mode
63 "Define a deftest defmacro for tests prefixed with NAME. MODE
64 is called to set up tests' environments. EXTENSION denotes the
65 suffix for tests' fixture files. If NO-FIXTURE is non-nil, don't
66 use a fixture. If ENABLE-CONTEXT-COLORING-MODE is non-nil,
67 `context-coloring-mode' is activated before tests. Functions
68 BEFORE-EACH and AFTER-EACH run before the major mode is activated
69 before each test, and after each test, even if an error is
71 (declare (indent defun))
72 (let ((macro-name (intern (format "context-coloring-test-deftest%s"
74 ;; No name means no dash.
76 (t (format "-%s" name)))))))
77 `(cl-defmacro ,macro-name (name
82 (declare (indent defun))
83 ;; Commas in nested backquotes are not evaluated. Binding the variables
84 ;; here is probably the cleanest workaround.
86 (before-each ',before-each)
87 (after-each ',after-each)
88 (test-name (intern (format ,(format "%s-%%s"
91 (t "generic"))) name)))
93 (fixture (format "./fixtures/%s" fixture))
94 (,no-fixture "./fixtures/empty")
95 (t (format ,(format "./fixtures/%%s.%s" extension) name)))))
96 ,@`((let ((enable-context-coloring-mode ,enable-context-coloring-mode))
97 `(ert-deftest ,test-name ()
98 (context-coloring-test-with-fixture
100 (when ,before-each (funcall ,before-each))
102 (when ,before (funcall ,before))
103 (when ,enable-context-coloring-mode (context-coloring-mode))
107 (when ,after (funcall ,after))
108 (when ,after-each (funcall ,after-each)))))))))))
110 (context-coloring-test-define-deftest nil
111 :mode #'fundamental-mode
114 (defun context-coloring-test-js2-mode ()
115 "Enable js2-mode and parse synchronously."
119 (context-coloring-test-define-deftest javascript
120 :mode #'context-coloring-test-js2-mode
122 :enable-context-coloring-mode t
123 :before-each (lambda ()
124 (setq js2-mode-show-parse-errors nil)
125 (setq js2-mode-show-strict-warnings nil)))
127 (context-coloring-test-define-deftest emacs-lisp
128 :mode #'emacs-lisp-mode
130 :enable-context-coloring-mode t)
132 (context-coloring-test-define-deftest eval-expression
133 :mode #'fundamental-mode
137 ;;; Assertion functions
139 (defun context-coloring-test-get-last-message ()
140 "Get the last message in the current messages bufffer."
141 (let ((messages (split-string
142 (buffer-substring-no-properties
146 (car (nthcdr (- (length messages) 2) messages))))
148 (defun context-coloring-test-assert-message (expected buffer)
149 "Assert that message EXPECTED is at the end of BUFFER."
150 (when (null (get-buffer buffer))
154 "Expected buffer `%s' to have message \"%s\", "
155 "but the buffer did not have any messages.")
157 (with-current-buffer buffer
158 (let ((message (context-coloring-test-get-last-message)))
159 (when (not (equal message expected))
163 "Expected buffer `%s' to have message \"%s\", "
164 "but instead it was \"%s\"")
168 (defun context-coloring-test-assert-not-message (expected buffer)
169 "Assert that message EXPECTED is not at the end of BUFFER."
170 (when (get-buffer buffer)
171 (with-current-buffer buffer
172 (let ((message (context-coloring-test-get-last-message)))
173 (when (equal message expected)
177 "Expected buffer `%s' not to have message \"%s\", "
179 buffer expected)))))))
181 (defun context-coloring-test-assert-error (body error-message)
182 "Assert that BODY signals ERROR-MESSAGE."
183 (let ((error-signaled-p nil))
188 (setq error-signaled-p t)
189 (when (not (string-equal (cadr err) error-message))
190 (ert-fail (format (concat "Expected the error \"%s\" to be thrown, "
191 "but instead it was \"%s\".")
194 (when (not error-signaled-p)
195 (ert-fail "Expected an error to be thrown, but there wasn't."))))
198 ;;; Miscellaneous tests
200 (defmacro context-coloring-test-define-derived-mode (name)
201 "Define a derived mode exclusively for any test with NAME."
202 (let ((name (intern (format "context-coloring-test-%s-mode" name))))
203 `(define-derived-mode ,name fundamental-mode "Testing")))
205 (defvar context-coloring-test-caused-p nil
206 "If non-nil, coloring was caused.")
208 (defmacro context-coloring-test-assert-causes-coloring (&rest body)
209 "Assert that BODY causes coloring."
211 ;; Gross, but I want this to pass on 24.3.
212 (ad-add-advice #'context-coloring-colorize
213 '(assert-causes-coloring
216 (setq context-coloring-test-caused-p t))))
219 (ad-activate #'context-coloring-colorize)
221 (when (not context-coloring-test-caused-p)
222 (ert-fail "Expected to have colorized, but it didn't."))))
224 (defun context-coloring-test-cleanup-assert-causes-coloring ()
225 "Undo `context-coloring-test-assert-causes-coloring'."
226 (ad-unadvise #'context-coloring-colorize)
227 (setq context-coloring-test-caused-p nil))
229 (context-coloring-test-define-derived-mode mode-startup)
231 (context-coloring-test-deftest mode-startup
233 (context-coloring-define-dispatch
235 :modes '(context-coloring-test-mode-startup-mode)
237 (context-coloring-test-mode-startup-mode)
238 (context-coloring-test-assert-causes-coloring
239 (context-coloring-mode)))
241 (context-coloring-test-cleanup-assert-causes-coloring)))
243 (context-coloring-test-define-derived-mode change-detection)
245 (context-coloring-test-deftest change-detection
247 (context-coloring-define-dispatch
249 :modes '(context-coloring-test-change-detection-mode)
251 :setup #'context-coloring-setup-idle-change-detection
252 :teardown #'context-coloring-teardown-idle-change-detection)
253 (context-coloring-test-change-detection-mode)
254 (context-coloring-mode)
255 (context-coloring-test-assert-causes-coloring
257 ;; Simply cannot figure out how to trigger an idle timer; would much rather
258 ;; test that. But (current-idle-time) always returns nil in these tests.
259 (context-coloring-maybe-colorize-with-buffer (current-buffer))))
261 (context-coloring-test-cleanup-assert-causes-coloring)))
263 (context-coloring-test-deftest unsupported-mode
265 (context-coloring-mode)
266 (context-coloring-test-assert-message
267 "Context coloring is unavailable here"
270 (context-coloring-test-deftest derived-mode
272 (lisp-interaction-mode)
273 (context-coloring-mode)
274 (context-coloring-test-assert-not-message
275 "Context coloring is unavailable here"
278 (context-coloring-test-deftest unavailable-message-ignored
280 (minibuffer-with-setup-hook
282 (context-coloring-mode)
283 (context-coloring-test-assert-not-message
284 "Context coloring is unavailable here"
291 (context-coloring-test-define-derived-mode define-dispatch-error)
293 (context-coloring-test-deftest define-dispatch-error
295 (context-coloring-test-assert-error
297 (context-coloring-define-dispatch
298 'define-dispatch-no-modes))
299 "No mode or predicate defined for dispatch")
300 (context-coloring-test-assert-error
302 (context-coloring-define-dispatch
303 'define-dispatch-no-strategy
304 :modes '(context-coloring-test-define-dispatch-error-mode)))
305 "No colorizer defined for dispatch")))
307 (context-coloring-test-define-derived-mode disable-mode)
309 (context-coloring-test-deftest disable-mode
312 (context-coloring-define-dispatch
314 :modes '(context-coloring-test-disable-mode-mode)
318 (context-coloring-test-disable-mode-mode)
319 (context-coloring-mode)
320 (context-coloring-mode -1)
321 (when (not torn-down)
322 (ert-fail "Expected teardown function to have been called, but it wasn't.")))))
324 (defun context-coloring-test-assert-maximum-face (expected)
325 "Assert that `context-coloring-maximum-face' is EXPECTED."
326 (when (not (= context-coloring-maximum-face expected))
327 (ert-fail (format "Expected maximum face to be %s, but it was %s"
328 expected context-coloring-maximum-face))))
330 (deftheme context-coloring-test-custom-theme)
332 (context-coloring-test-define-derived-mode custom-theme)
334 (context-coloring-test-deftest custom-theme
336 (custom-theme-set-faces
337 'context-coloring-test-custom-theme
338 '(context-coloring-level-0-face ((t :foreground "#aaaaaa")))
339 '(context-coloring-level-1-face ((t :foreground "#bbbbbb"))))
341 '(context-coloring-level-0-face ((t :foreground "#aaaaaa"))))
342 (enable-theme 'context-coloring-test-custom-theme)
343 (context-coloring-define-dispatch
345 :modes '(context-coloring-test-custom-theme-mode)
347 (context-coloring-test-custom-theme-mode)
348 (context-coloring-colorize)
349 (context-coloring-test-assert-maximum-face 1)
350 ;; This theme should now be ignored in favor of the `user' theme.
351 (custom-theme-reset-faces
352 'context-coloring-test-custom-theme
353 '(context-coloring-level-0-face nil)
354 '(context-coloring-level-1-face nil))
355 (context-coloring-colorize)
356 ;; Maximum face for `user'.
357 (context-coloring-test-assert-maximum-face 0)
358 ;; Now `user' should be ignored too.
360 '(context-coloring-level-0-face nil))
361 (context-coloring-colorize)
362 ;; Expect the package's defaults.
363 (context-coloring-test-assert-maximum-face
364 context-coloring-default-maximum-face))
367 '(context-coloring-level-0-face nil))
368 (disable-theme 'context-coloring-test-custom-theme)))
373 (defun context-coloring-test-face-to-level (face)
374 "Convert FACE symbol to its corresponding level, or nil."
376 (let* ((face-string (symbol-name face))
377 (matches (string-match
378 context-coloring-level-face-regexp
381 (string-to-number (match-string 1 face-string))))))
383 (defun context-coloring-test-assert-position-level (position level)
384 "Assert that POSITION has LEVEL."
385 (let* ((face (get-text-property position 'face))
386 (actual-level (context-coloring-test-face-to-level face)))
387 (when (not (= level actual-level))
388 (ert-fail (format (concat "Expected level at position %s, "
389 "which is \"%s\", to be %s; "
392 (buffer-substring-no-properties position (1+ position)) level
395 (defun context-coloring-test-assert-position-face (position face-regexp)
396 "Assert that the face at POSITION satisfies FACE-REGEXP."
397 (let ((face (get-text-property position 'face)))
399 ;; Pass a non-string to do an `equal' check (against a symbol or nil).
400 (unless (stringp face-regexp)
401 (not (equal face-regexp face)))
402 ;; Otherwise do the matching.
403 (when (stringp face-regexp)
404 (not (string-match-p face-regexp (symbol-name face)))))
405 (ert-fail (format (concat "Expected face at position %s, "
406 "which is \"%s\", to be %s; "
409 (buffer-substring-no-properties position (1+ position)) face-regexp
412 (defun context-coloring-test-assert-position-comment (position)
413 "Assert that the face at POSITION is a comment."
414 (context-coloring-test-assert-position-face
415 position "\\`font-lock-comment\\(-delimiter\\)?-face\\'"))
417 (defun context-coloring-test-assert-position-constant-comment (position)
418 "Assert that the face at POSITION is a constant comment."
419 (context-coloring-test-assert-position-face position '(font-lock-constant-face
420 font-lock-comment-face)))
422 (defun context-coloring-test-assert-position-string (position)
423 "Assert that the face at POSITION is a string."
424 (context-coloring-test-assert-position-face position 'font-lock-string-face))
426 (defun context-coloring-test-assert-position-nil (position)
427 "Assert that the face at POSITION is nil."
428 (context-coloring-test-assert-position-face position nil))
430 (defun context-coloring-test-assert-coloring (map)
431 "Assert that the current buffer's coloring will match MAP.
433 MAP's newlines should correspond to the current fixture.
435 The following characters appearing in MAP assert coloring for
436 corresponding points in the fixture:
438 0-9: Level equals number.
439 C: Face is constant comment.
444 Any other characters are discarded. Characters \"x\" and any
445 other non-letters are guaranteed to always be discarded."
446 ;; Omit the superfluous, formatting-related leading newline. Can't use
447 ;; `save-excursion' here because if an assertion fails it will cause future
448 ;; tests to get messed up.
449 (goto-char (point-min))
450 (let* ((map (substring map 1))
454 (while (< index (length map))
455 (setq char-string (substring map index (1+ index)))
456 (setq char (string-to-char char-string))
465 (context-coloring-test-assert-position-level
466 (point) (string-to-number char-string))
468 ;; 'C' = Constant comment
470 (context-coloring-test-assert-position-constant-comment (point))
474 (context-coloring-test-assert-position-comment (point))
478 (context-coloring-test-assert-position-nil (point))
482 (context-coloring-test-assert-position-string (point))
486 (setq index (1+ index)))))
488 (context-coloring-test-deftest-javascript function-scopes
490 (context-coloring-test-assert-coloring "
491 000 0 0 11111111 11 110
493 111 1 1 22222222 22 221
497 (context-coloring-test-deftest-javascript global
499 (context-coloring-test-assert-coloring "
504 (context-coloring-test-deftest-javascript block-scopes
506 (context-coloring-test-assert-coloring "
524 (setq context-coloring-javascript-block-scopes t))
526 (setq context-coloring-javascript-block-scopes nil)))
528 (context-coloring-test-deftest-javascript catch
530 (context-coloring-test-assert-coloring "
540 (context-coloring-test-deftest-javascript key-names
542 (context-coloring-test-assert-coloring "
550 (context-coloring-test-deftest-javascript property-lookup
552 (context-coloring-test-assert-coloring "
559 (context-coloring-test-deftest-javascript key-values
561 (context-coloring-test-assert-coloring "
571 (context-coloring-test-deftest-javascript syntactic-comments-and-strings
573 (context-coloring-test-assert-coloring "
578 :fixture "comments-and-strings.js")
580 (context-coloring-test-deftest-javascript syntactic-comments
582 (context-coloring-test-assert-coloring "
587 :fixture "comments-and-strings.js"
589 (setq context-coloring-syntactic-strings nil))
591 (setq context-coloring-syntactic-strings t)))
593 (context-coloring-test-deftest-javascript syntactic-strings
595 (context-coloring-test-assert-coloring "
600 :fixture "comments-and-strings.js"
602 (setq context-coloring-syntactic-comments nil))
604 (setq context-coloring-syntactic-comments t)))
606 (context-coloring-test-deftest-javascript unterminated-comment
607 ;; As long as `add-text-properties' doesn't signal an error, this test passes.
610 (defun context-coloring-test-assert-javascript-elevated-level ()
611 "Assert that the \"initial-level.js\" file has elevated scope."
612 (context-coloring-test-assert-coloring "
614 111 1 1 0000001xxx11"))
616 (defun context-coloring-test-assert-javascript-global-level ()
617 "Assert that the \"initial-level.js\" file has global scope."
618 (context-coloring-test-assert-coloring "
620 000 0 0 0000000xxx00"))
622 (context-coloring-test-deftest-javascript initial-level
624 (context-coloring-test-assert-javascript-elevated-level))
625 :fixture "initial-level.js"
627 (setq context-coloring-initial-level 1))
629 (setq context-coloring-initial-level 0)))
631 (defun context-coloring-test-setup-top-level-scope (string)
632 "Make STRING the first line and colorize again."
633 (goto-char (point-min))
636 ;; Reparsing triggers recoloring.
639 (context-coloring-test-deftest-javascript top-level-scope
641 (let ((positive-indicators
642 (list "#!/usr/bin/env node"
643 "/*jslint node: true */"
644 "// jshint node: true"
645 "/*eslint-env node */"
651 (list "// Blah blah jshint blah."
654 "var require; require('a')")))
655 (dolist (indicator positive-indicators)
656 (context-coloring-test-setup-top-level-scope indicator)
657 (context-coloring-test-assert-javascript-elevated-level))
658 (dolist (indicator negative-indicators)
659 (context-coloring-test-setup-top-level-scope indicator)
660 (context-coloring-test-assert-javascript-global-level))))
661 :fixture "initial-level.js")
663 (context-coloring-test-deftest-emacs-lisp defun
665 (context-coloring-test-assert-coloring "
666 111111 000 1111 111 111111111 1111
667 11 111 111 111 000011
675 (context-coloring-test-deftest-emacs-lisp defadvice
677 (context-coloring-test-assert-coloring "
678 1111111111 0 1111111 111111 11111 111 111111111
682 (context-coloring-test-deftest-emacs-lisp lambda
684 (context-coloring-test-assert-coloring "
685 00000000 1111111 1111
686 11111111 11 2222222 2222
687 222 22 12 2221 111 0 00")))
689 (context-coloring-test-deftest-emacs-lisp quote
691 (context-coloring-test-assert-coloring "
692 (xxxxx 0000000 00 00000)
693 (xxx () (xxxxxxxxx (,0000)))
698 111111 1 1111111111 11 111 1 111 1 00001 10000 11 00001 1 10000
706 (xxxxxx () 111111 11111)")))
708 (context-coloring-test-deftest-emacs-lisp splice
710 (context-coloring-test-assert-coloring "
712 111111 00001 100001)")))
714 (context-coloring-test-deftest-emacs-lisp comment
716 ;; Just check that the comment isn't parsed syntactically.
717 (context-coloring-test-assert-coloring "
719 (xx (x xxxxx-xxxx xx) cccccccccc
720 11 00000-0000 11))) cccccccccc")))
722 (context-coloring-test-deftest-emacs-lisp string
724 (context-coloring-test-assert-coloring "
726 (xxxxxx x x sss 1 0 sssss 0 1 sssssss11")))
728 (context-coloring-test-deftest-emacs-lisp ignored
730 (context-coloring-test-assert-coloring "
732 (x x 1 11 11 111 111 11 11 11 1 111 (1 1 1)))")))
734 (context-coloring-test-deftest-emacs-lisp sexp
736 (context-coloring-test-assert-coloring "
742 (context-coloring-test-deftest-emacs-lisp let
744 (context-coloring-test-assert-coloring "
757 (context-coloring-test-deftest-emacs-lisp empty-varlist
759 (context-coloring-test-assert-coloring "
767 (context-coloring-test-deftest-emacs-lisp varlist-spacing
769 (context-coloring-test-assert-coloring "
779 (context-coloring-test-deftest-emacs-lisp let*
781 (context-coloring-test-assert-coloring "
791 2222 1 1 2 2 2 000022
792 1111 1 1 1 0 0 000011"))
793 :fixture "let-star.el")
795 (context-coloring-test-deftest-emacs-lisp macroexp-let2
797 (context-coloring-test-assert-coloring "
799 222222222-2222 00000000-00000000-0 2 111
802 (11111111-1111 00000000-00000000-0)
805 (context-coloring-test-deftest-emacs-lisp cond
807 (context-coloring-test-assert-coloring "
817 (context-coloring-test-deftest-emacs-lisp condition-case
819 (context-coloring-test-assert-coloring "
823 1111111 111111 111 000011
825 (111111111-1111-111111-11111 111
831 (context-coloring-test-deftest-emacs-lisp dolist
833 (context-coloring-test-assert-coloring "
835 2222222 2222 1111 2222222
836 3333333 33 33 222 1111 2222223321")))
838 (defun context-coloring-test-insert-unread-space ()
839 "Simulate the insertion of a space as if by a user."
840 (setq unread-command-events (cons '(t . 32)
841 unread-command-events)))
843 (defun context-coloring-test-remove-faces ()
844 "Remove all faces in the current buffer."
845 (remove-text-properties (point-min) (point-max) '(face nil)))
847 (context-coloring-test-deftest-emacs-lisp iteration
849 (let ((context-coloring-elisp-sexps-per-pause 2))
850 (context-coloring-colorize)
851 (context-coloring-test-assert-coloring "
854 (context-coloring-test-remove-faces)
855 (context-coloring-test-insert-unread-space)
856 (context-coloring-colorize)
857 ;; Coloring is interrupted after the first "sexp" (the comment in this
859 (context-coloring-test-assert-coloring "
863 (context-coloring-test-deftest-emacs-lisp changed
865 (context-coloring-test-remove-faces)
867 (goto-char (point-min))
868 (forward-line (1- 3))
870 ;; Mock `pos-visible-in-window-p' because in batch mode `get-buffer-window'
871 ;; returns nil. Emacs must not have a window in that environment.
872 (cl-letf (((symbol-function 'pos-visible-in-window-p)
876 ;; First and third calls start from center. Second and
877 ;; fourth calls are made immediately after moving past
878 ;; the first defun in either direction "off screen".
884 (setq calls (1+ calls)))))))
885 (context-coloring-colorize))
886 (context-coloring-test-assert-coloring "
891 nnnnn n nnn nnnnnnnn")))
893 (context-coloring-test-deftest-emacs-lisp unbalanced-parenthesis
895 (context-coloring-test-assert-coloring "
899 (context-coloring-test-deftest-eval-expression let
901 (minibuffer-with-setup-hook
903 ;; Perform the test in a hook as it's the only way I know of examining
904 ;; the minibuffer's contents. The contents are implicitly submitted,
905 ;; so we have to ignore the errors in the arbitrary test subject code.
906 (insert "(ignore-errors (let (a) (message a free)))")
907 (context-coloring-colorize)
908 (context-coloring-test-assert-coloring "
909 xxxx: 0000000-000000 1111 111 11111111 1 0000110"))
910 ;; Simulate user input because `call-interactively' is blocking and
911 ;; doesn't seem to run the hook.
914 [?\C-u] ;; Don't output the result of the arbitrary test subject code.
917 (provide 'context-coloring-test)
919 ;;; context-coloring-test.el ends here