commit 148100d98319499f0ac6f57b8be08cbd14884a5c (HEAD, refs/remotes/origin/master) Author: Tom Tromey Date: Sat Feb 4 13:22:39 2017 -0700 typo fix (css--colon-inside-selector-p): Fix typo in docstring. diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index 65a599d6d4..0c7d76f792 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -755,7 +755,7 @@ cannot be completed sensibly: `custom-ident', "Return t if point looks to be inside a CSS selector. This function is intended to be good enough to help SMIE during tokenization, but should not be regarded as a reliable function -for determining wheter point is within a selector." +for determining whether point is within a selector." (save-excursion (re-search-forward "[{};)]" nil t) (eq (char-before) ?\{))) commit 394fc3fd030b8fa2f9b97c2ef3fa3b16e6f29474 Author: Tom Tromey Date: Tue Jan 17 21:50:14 2017 -0700 Set comment-multi-line in js-mode Bug#6806: * lisp/progmodes/js.el (js-mode): Set comment-multi-line to t. * test/lisp/progmodes/js-tests.el (js-mode-auto-fill): New test. diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el index 74dd4add9e..e42e01481b 100644 --- a/lisp/progmodes/js.el +++ b/lisp/progmodes/js.el @@ -3849,6 +3849,7 @@ If one hasn't been set, or if it's stale, prompt for a new one." comment-start-skip "\\(//+\\|/\\*+\\)\\s *") (setq-local comment-line-break-function #'c-indent-new-comment-line) (setq-local c-block-comment-start-regexp "/\\*") + (setq-local comment-multi-line t) (setq-local electric-indent-chars (append "{}():;," electric-indent-chars)) ;FIXME: js2-mode adds "[]*". diff --git a/test/lisp/progmodes/js-tests.el b/test/lisp/progmodes/js-tests.el index 84749efa45..7cb737c30e 100644 --- a/test/lisp/progmodes/js-tests.el +++ b/test/lisp/progmodes/js-tests.el @@ -85,6 +85,20 @@ if (!/[ (:,='\"]/.test(value)) { (should (= (current-column) x)) (forward-line)))) +(ert-deftest js-mode-auto-fill () + (with-temp-buffer + (js-mode) + (setq fill-column 70) + (insert "/* ") + (dotimes (_ 16) + (insert "test ")) + (do-auto-fill) + ;; The bug is that, after auto-fill, the second line starts with + ;; "/*", whereas it should start with " * ". + (goto-char (point-min)) + (forward-line) + (should (looking-at " \\* test")))) + (provide 'js-tests) ;;; js-tests.el ends here commit caf31fb5f53aef47bea1cd2e741f5d9c254c1a81 Author: Simen Heggestøyl Date: Sat Feb 4 20:33:58 2017 +0100 * test/manual/indent/scss-mode.scss: Fix indentation diff --git a/test/manual/indent/scss-mode.scss b/test/manual/indent/scss-mode.scss index d2a4f5cc1d..f9911ad11b 100644 --- a/test/manual/indent/scss-mode.scss +++ b/test/manual/indent/scss-mode.scss @@ -16,20 +16,20 @@ nav { } } nav ul { - margin: 0; - padding: 0; - list-style: none; + margin: 0; + padding: 0; + list-style: none; } nav li { - display: inline-block; + display: inline-block; } nav a var { - display: block; - padding: 6px 12px; - text-decoration: none; + display: block; + padding: 6px 12px; + text-decoration: none; } $name: foo; @@ -67,12 +67,12 @@ button { // bug:21230 $list: ( - ('a', #000000, #fff) - ('b', #000000, #fff) - ('c', #000000, #fff) - ('d', #000000, #fff) - ('e', #000000, #fff) - ('f', #000000, #fff) + ('a', #000000, #fff) + ('b', #000000, #fff) + ('c', #000000, #fff) + ('d', #000000, #fff) + ('e', #000000, #fff) + ('f', #000000, #fff) ); // bug:13425 commit f6ff7bb1fcd062fe4ebf6c89890524110501583e Author: Simen Heggestøyl Date: Thu Feb 2 20:05:32 2017 +0100 Fix indentation of multiline CSS property values * lisp/textmodes/css-mode.el (css-smie-grammar): Give colons belonging to properties higher precedence. (css--colon-inside-selector-p, css--colon-inside-funcall): New functions for helping SMIE during tokenization. (css-smie--forward-token, css-smie--backward-token): Distinguish colons belonging to properties from other colons. * test/manual/indent/css-mode.css: Add tests for the changes above. * test/manual/indent/scss-mode.scss: Ditto. diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index 19f74daec6..65a599d6d4 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -32,10 +32,11 @@ ;;; Code: +(require 'eww) (require 'seq) (require 'sgml-mode) (require 'smie) -(require 'eww) +(require 'subr-x) (defgroup css nil "Cascading Style Sheets (CSS) editing mode." @@ -741,7 +742,30 @@ cannot be completed sensibly: `custom-ident', (defconst css-smie-grammar (smie-prec2->grammar - (smie-precs->prec2 '((assoc ";") (assoc ",") (left ":"))))) + (smie-precs->prec2 + '((assoc ";") + ;; Colons that belong to a CSS property. These get a higher + ;; precedence than other colons, such as colons in selectors, + ;; which are represented by a plain ":" token. + (left ":-property") + (assoc ",") + (assoc ":"))))) + +(defun css--colon-inside-selector-p () + "Return t if point looks to be inside a CSS selector. +This function is intended to be good enough to help SMIE during +tokenization, but should not be regarded as a reliable function +for determining wheter point is within a selector." + (save-excursion + (re-search-forward "[{};)]" nil t) + (eq (char-before) ?\{))) + +(defun css--colon-inside-funcall () + "Return t if point is inside a function call." + (when-let (opening-paren-pos (nth 1 (syntax-ppss))) + (save-excursion + (goto-char opening-paren-pos) + (eq (char-after) ?\()))) (defun css-smie--forward-token () (cond @@ -755,7 +779,13 @@ cannot be completed sensibly: `custom-ident', ";") ((progn (forward-comment (point-max)) (looking-at "[;,:]")) - (forward-char 1) (match-string 0)) + (forward-char 1) + (if (equal (match-string 0) ":") + (if (or (css--colon-inside-selector-p) + (css--colon-inside-funcall)) + ":" + ":-property") + (match-string 0))) (t (smie-default-forward-token)))) (defun css-smie--backward-token () @@ -766,7 +796,13 @@ cannot be completed sensibly: `custom-ident', ((and (eq (char-before) ?\}) (scss-smie--not-interpolation-p) (> pos (point))) ";") ((memq (char-before) '(?\; ?\, ?\:)) - (forward-char -1) (string (char-after))) + (forward-char -1) + (if (eq (char-after) ?\:) + (if (or (css--colon-inside-selector-p) + (css--colon-inside-funcall)) + ":" + ":-property") + (string (char-after)))) (t (smie-default-backward-token))))) (defun css-smie-rules (kind token) diff --git a/test/manual/indent/css-mode.css b/test/manual/indent/css-mode.css index 3a00739bfc..0845c02c29 100644 --- a/test/manual/indent/css-mode.css +++ b/test/manual/indent/css-mode.css @@ -43,3 +43,30 @@ article:hover { color: black; } + +/* bug:13425 */ +div:first-child, +div:last-child, +div[disabled], +div::before { + font: 15px "Helvetica Neue", + Helvetica, + Arial, + "Nimbus Sans L", + sans-serif; + font: 15px "Helvetica Neue", Helvetica, Arial, + "Nimbus Sans L", sans-serif; + transform: matrix(1.0, 2.0, + 3.0, 4.0, + 5.0, 6.0); + transform: matrix( + 1.0, 2.0, + 3.0, 4.0, + 5.0, 6.0 + ); +} +@font-face { + src: url("Sans-Regular.eot") format("eot"), + url("Sans-Regular.woff") format("woff"), + url("Sans-Regular.ttf") format("truetype"); +} diff --git a/test/manual/indent/scss-mode.scss b/test/manual/indent/scss-mode.scss index e1ec90a529..d2a4f5cc1d 100644 --- a/test/manual/indent/scss-mode.scss +++ b/test/manual/indent/scss-mode.scss @@ -74,3 +74,21 @@ $list: ( ('e', #000000, #fff) ('f', #000000, #fff) ); + +// bug:13425 +div:first-child, +div:last-child { + @include foo-mixin( + $foo: 'foo', + $bar: 'bar', + ); + + font: 15px "Helvetica Neue", Helvetica, Arial, + "Nimbus Sans L", sans-serif; + + div:first-child, + div:last-child { + font: 15px "Helvetica Neue", Helvetica, Arial, + "Nimbus Sans L", sans-serif; + } +} commit ff4dd0d39c3f5dfb8f4988f840c2c05621db32db Author: Gemini Lasswell Date: Sat Feb 4 13:55:47 2017 +0200 Add tests for lisp/kmacro.el * test/lisp/kmacro-tests.el: New file. (Bug#24939) diff --git a/test/lisp/kmacro-tests.el b/test/lisp/kmacro-tests.el new file mode 100644 index 0000000000..5124cbbf96 --- /dev/null +++ b/test/lisp/kmacro-tests.el @@ -0,0 +1,890 @@ +;;; kmacro-tests.el --- Tests for kmacro.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Author: Gemini Lasswell + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see . + +;;; Commentary: + +;;; Code: + +(require 'kmacro) +(require 'ert) +(require 'ert-x) + +;;; Test fixtures: + +(defmacro kmacro-tests-with-kmacro-clean-slate (&rest body) + "Create a clean environment for a kmacro test BODY to run in." + (declare (debug (body))) + `(cl-letf* ((kmacro-execute-before-append t) + (kmacro-ring-max 8) + (kmacro-repeat-no-prefix t) + (kmacro-call-repeat-key nil) + (kmacro-call-repeat-with-arg nil) + + (kbd-macro-termination-hook nil) + (defining-kbd-macro nil) + (executing-kbd-macro nil) + (executing-kbd-macro-index 0) + (last-kbd-macro nil) + + (kmacro-ring nil) + + (kmacro-counter 0) + (kmacro-default-counter-format "%d") + (kmacro-counter-format "%d") + (kmacro-counter-format-start "%d") + (kmacro-counter-value-start 0) + (kmacro-last-counter 0) + (kmacro-initial-counter-value nil) + + (kmacro-tests-macros nil) + (kmacro-tests-events nil) + (kmacro-tests-sequences nil)) + (advice-add 'end-kbd-macro :after #'kmacro-tests-end-macro-advice) + (advice-add 'read-event :around #'kmacro-tests-read-event-advice ) + (advice-add 'read-key-sequence :around #'kmacro-tests-read-key-sequence-advice) + (unwind-protect + (ert-with-test-buffer (:name "") + (switch-to-buffer (current-buffer)) + ,@body) + (advice-remove 'read-key-sequence #'kmacro-tests-read-key-sequence-advice) + (advice-remove 'read-event #'kmacro-tests-read-event-advice) + (advice-remove 'end-kbd-macro #'kmacro-tests-end-macro-advice)))) + +(defmacro kmacro-tests-deftest (name _args docstring &rest keys-and-body) + "Define a kmacro unit test. +NAME is the name of the test, _ARGS should be nil, and DOCSTRING +is required. To avoid having to duplicate ert's keyword parsing +here, its keywords and values (if any) must be inside a list +after the docstring, preceding the body, here combined with the +body in KEYS-AND-BODY." + (declare (debug (&define name sexp stringp + [&optional (&rest &or [keywordp sexp])] + def-body)) + (doc-string 3) + (indent 2)) + + (let* ((keys (when (and (listp (car keys-and-body)) + (keywordp (caar keys-and-body))) + (car keys-and-body))) + (body (if keys (cdr keys-and-body) + keys-and-body))) + `(ert-deftest ,name () + ,docstring ,@keys + (kmacro-tests-with-kmacro-clean-slate ,@body)))) + +(defvar kmacro-tests-keymap + (let ((map (make-sparse-keymap))) + (dotimes (i 26) + (define-key map (string (+ ?a i)) 'self-insert-command)) + (dotimes (i 10) + (define-key map (string (+ ?0 i)) 'self-insert-command)) + ;; Define a few key sequences of different lengths. + (dolist (item '(("\C-a" . beginning-of-line) + ("\C-b" . backward-char) + ("\C-e" . end-of-line) + ("\C-f" . forward-char) + ("\C-r" . isearch-backward) + ("\C-u" . universal-argument) + ("\C-w" . kill-region) + ("\C-SPC" . set-mark-command) + ("\M-w" . kill-ring-save) + ("\M-x" . execute-extended-command) + ("\C-cd" . downcase-word) + ("\C-cxu" . upcase-word) + ("\C-cxq" . quoted-insert) + ("\C-cxi" . kmacro-insert-counter) + ("\C-x\C-k" . kmacro-keymap))) + (define-key map (car item) (cdr item))) + map) + "Keymap to use for testing keyboard macros. +This is used to obtain consistent results even if tests are run +in an environment with rebound keys.") + +(defvar kmacro-tests-events nil + "Input events used by the kmacro test in progress.") + +(defun kmacro-tests-read-event-advice (orig-func &rest args) + "Pop and return an event from `kmacro-tests-events'. +Return the result of calling ORIG-FUNC with ARGS if +`kmacro-tests-events' is empty, or if a keyboard macro is +running." + (if (or executing-kbd-macro (null kmacro-tests-events)) + (apply orig-func args) + (pop kmacro-tests-events))) + +(defvar kmacro-tests-sequences nil + "Input sequences used by the kmacro test in progress.") + +(defun kmacro-tests-read-key-sequence-advice (orig-func &rest args) + "Pop and return a string from `kmacro-tests-sequences'. +Return the result of calling ORIG-FUNC with ARGS if +`kmacro-tests-sequences' is empty, or if a keyboard macro is +running." + (if (or executing-kbd-macro (null kmacro-tests-sequences)) + (apply orig-func args) + (pop kmacro-tests-sequences))) + +(defvar kmacro-tests-macros nil + "Keyboard macros (in vector form) used by the kmacro test in progress.") + +(defun kmacro-tests-end-macro-advice (&rest _args) + "Pop a macro from `kmacro-tests-macros' and assign it to `last-kbd-macro'. +If `kmacro-tests-macros' is empty, do nothing." + (when kmacro-tests-macros + (setq last-kbd-macro (pop kmacro-tests-macros)))) + +;;; Some more powerful expectations: + +(defmacro kmacro-tests-should-insert (value &rest body) + "Verify that VALUE is inserted by the execution of BODY. +Execute BODY, then check that the string VALUE was inserted +into the current buffer at point." + (declare (debug (stringp body)) + (indent 1)) + (let ((g-p (cl-gensym)) + (g-bsize (cl-gensym))) + `(let ((,g-p (point)) + (,g-bsize (buffer-size))) + ,@body + (should (equal (buffer-substring ,g-p (point)) ,value)) + (should (equal (- (buffer-size) ,g-bsize) (length ,value)))))) + +(defmacro kmacro-tests-should-match-message (value &rest body) + "Verify that a message matching VALUE is issued while executing BODY. +Execute BODY, and then if there is not a regexp match between +VALUE and any text written to *Messages* during the execution, +cause the current test to fail." + (declare (debug (form body)) + (indent 1)) + (let ((g-captured-messages (cl-gensym))) + `(ert-with-message-capture ,g-captured-messages + ,@body + (should (string-match-p ,value ,g-captured-messages))))) + +;;; Tests: + +(kmacro-tests-deftest kmacro-tests-test-insert-counter-01-nil () + "`kmacro-insert-counter' adds one to macro counter with nil arg." + (kmacro-tests-should-insert "0" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil))) + (kmacro-tests-should-insert "1" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil)))) + +(kmacro-tests-deftest kmacro-tests-test-insert-counter-02-int () + "`kmacro-insert-counter' increments by value of list argument." + (kmacro-tests-should-insert "0" + (kmacro-tests-simulate-command '(kmacro-insert-counter 2))) + (kmacro-tests-should-insert "2" + (kmacro-tests-simulate-command '(kmacro-insert-counter 3))) + (kmacro-tests-should-insert "5" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil)))) + +(kmacro-tests-deftest kmacro-tests-test-insert-counter-03-list () + "`kmacro-insert-counter' doesn't increment when given universal argument." + (kmacro-tests-should-insert "0" + (kmacro-tests-simulate-command '(kmacro-insert-counter (16)))) + (kmacro-tests-should-insert "0" + (kmacro-tests-simulate-command '(kmacro-insert-counter (4))))) + +(kmacro-tests-deftest kmacro-tests-test-insert-counter-04-neg () + "`kmacro-insert-counter' decrements with '- prefix argument" + (kmacro-tests-should-insert "0" + (kmacro-tests-simulate-command '(kmacro-insert-counter -))) + (kmacro-tests-should-insert "-1" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil)))) + +(kmacro-tests-deftest kmacro-tests-test-start-format-counter () + "`kmacro-insert-counter' uses start value and format." + (kmacro-tests-simulate-command '(kmacro-set-counter 10)) + (kmacro-tests-should-insert "10" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil))) + (kmacro-tests-should-insert "11" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil))) + (kmacro-set-format "c=%s") + (kmacro-tests-simulate-command '(kmacro-set-counter 50)) + (kmacro-tests-should-insert "c=50" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil)))) + +(kmacro-tests-deftest kmacro-tests-test-start-macro-when-defining-macro () + "Starting a macro while defining a macro does not start a second macro." + (kmacro-tests-simulate-command '(kmacro-start-macro nil)) + ;; We should now be in the macro-recording state. + (should defining-kbd-macro) + (should-not last-kbd-macro) + ;; Calling it again should leave us in the same state. + (kmacro-tests-simulate-command '(kmacro-start-macro nil)) + (should defining-kbd-macro) + (should-not last-kbd-macro)) + + +(kmacro-tests-deftest kmacro-tests-set-macro-counter-while-defining () + "Use of the prefix arg with kmacro-start sets kmacro-counter." + ;; Give kmacro-start-macro an argument. + (kmacro-tests-simulate-command '(kmacro-start-macro 5)) + (should defining-kbd-macro) + ;; Verify that the counter is set to that value. + (kmacro-tests-should-insert "5" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil))) + ;; Change it while defining a macro. + (kmacro-tests-simulate-command '(kmacro-set-counter 1)) + (kmacro-tests-should-insert "1" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil))) + ;; Using universal arg to to set counter should reset to starting value. + (kmacro-tests-simulate-command '(kmacro-set-counter (4)) '(4)) + (kmacro-tests-should-insert "5" + (kmacro-tests-simulate-command '(kmacro-insert-counter nil)))) + + +(kmacro-tests-deftest kmacro-tests-start-insert-counter-appends-to-macro () + "Use of the universal arg appends to the previous macro." + (let ((kmacro-tests-macros (list (string-to-vector "hello")))) + ;; Start recording a macro. + (kmacro-tests-simulate-command '(kmacro-start-macro-or-insert-counter nil)) + ;; Make sure we are recording. + (should defining-kbd-macro) + ;; Call it again and it should insert the counter. + (kmacro-tests-should-insert "0" + (kmacro-tests-simulate-command '(kmacro-start-macro-or-insert-counter nil))) + ;; We should still be in the recording state. + (should defining-kbd-macro) + ;; End recording with repeat count. + (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 3)) + ;; Recording should be finished. + (should-not defining-kbd-macro) + ;; Now use prefix arg to append to the previous macro. + ;; This should run the previous macro first. + (kmacro-tests-should-insert "hello" + (kmacro-tests-simulate-command + '(kmacro-start-macro-or-insert-counter (4)))) + ;; Verify that the recording state has changed. + (should (equal defining-kbd-macro 'append)))) + +(kmacro-tests-deftest kmacro-tests-end-call-macro-prefix-args () + "kmacro-end-call-macro changes behavior based on prefix arg." + ;; "Record" two macros. + (dotimes (i 2) + (kmacro-tests-define-macro (vconcat (format "macro #%d" (1+ i))))) + ;; With no prefix arg, it should call the second macro. + (kmacro-tests-should-insert "macro #2" + (kmacro-tests-simulate-command '(kmacro-end-or-call-macro nil))) + ;; With universal arg, it should call the first one. + (kmacro-tests-should-insert "macro #1" + (kmacro-tests-simulate-command '(kmacro-end-or-call-macro (4))))) + +(kmacro-tests-deftest kmacro-tests-end-and-call-macro () + "Keyboard command to end and call macro works under various conditions." + ;; First, try it with no macro to record. + (setq kmacro-tests-macros '("")) + (kmacro-tests-simulate-command '(kmacro-start-macro nil)) + (condition-case err + (kmacro-tests-simulate-command '(kmacro-end-and-call-macro 2) 2) + (error (should (string= (cadr err) + "No kbd macro has been defined")))) + + ;; Check that it stopped defining and that no macro was recorded. + (should-not defining-kbd-macro) + (should-not last-kbd-macro) + + ;; Now try it while not recording, but first record a non-nil macro. + (kmacro-tests-define-macro "macro") + (kmacro-tests-should-insert "macro" + (kmacro-tests-simulate-command '(kmacro-end-and-call-macro nil)))) + +(kmacro-tests-deftest kmacro-tests-end-and-call-macro-mouse () + "Commands to end and call macro work under various conditions. +This is a regression test for Bug#24992." + (:expected-result :failed) + (cl-letf (((symbol-function #'mouse-set-point) #'ignore)) + ;; First, try it with no macro to record. + (setq kmacro-tests-macros '("")) + (kmacro-tests-simulate-command '(kmacro-start-macro nil)) + (condition-case err + (kmacro-tests-simulate-command '(kmacro-end-call-mouse 2) 2) + (error (should (string= (cadr err) + "No kbd macro has been defined")))) + + ;; Check that it stopped defining and that no macro was recorded. + (should-not defining-kbd-macro) + (should-not last-kbd-macro) + + ;; Now try it while not recording, but first record a non-nil macro. + (kmacro-tests-define-macro "macro") + (kmacro-tests-should-insert "macro" + (kmacro-tests-simulate-command '(kmacro-end-call-mouse nil))))) + +(kmacro-tests-deftest kmacro-tests-call-macro-hint-and-repeat () + "`kmacro-call-macro' gives hint in Messages and sets up repeat keymap. +This is a regression test for: Bug#3412, Bug#11817." + (kmacro-tests-define-macro [?m]) + (let ((kmacro-call-repeat-key t) + (kmacro-call-repeat-with-arg t) + (overriding-terminal-local-map overriding-terminal-local-map) + (last-input-event ?e)) + (message "") ; Clear the echo area. (Bug#3412) + (kmacro-tests-should-match-message "Type e to repeat macro" + (kmacro-tests-should-insert "mmmmmm" + (cl-letf (((symbol-function #'this-single-command-keys) (lambda () + [?\C-x ?e]))) + (kmacro-call-macro 3)) + ;; Check that it set up for repeat, and run the repeat. + (funcall (lookup-key overriding-terminal-local-map "e")))))) + +(kmacro-tests-deftest + kmacro-tests-run-macro-command-recorded-in-macro () + "No infinite loop if `kmacro-end-and-call-macro' is recorded in the macro. +\(Bug#15126)" + (:expected-result :failed) + (ert-skip "Skipping due to Bug#24921 (an ERT bug)") + (kmacro-tests-define-macro (vconcat "foo" [return] "\M-x" + "kmacro-end-and-call-macro")) + (use-local-map kmacro-tests-keymap) + (kmacro-tests-simulate-command '(kmacro-end-and-call-macro nil))) + + +(kmacro-tests-deftest kmacro-tests-test-ring-2nd-commands () + "2nd macro in ring is displayed and executed normally and on repeat." + (use-local-map kmacro-tests-keymap) + ;; Record one macro, with count. + (push (vconcat "\C-cxi" "\C-u\C-cxi") kmacro-tests-macros) + (kmacro-tests-simulate-command '(kmacro-start-macro 1)) + (kmacro-tests-simulate-command '(kmacro-end-macro nil)) + ;; Check that execute and display do nothing with no 2nd macro. + (kmacro-tests-should-insert "" + (kmacro-tests-simulate-command '(kmacro-call-ring-2nd nil))) + (kmacro-tests-should-match-message "Only one keyboard macro defined" + (kmacro-tests-simulate-command '(kmacro-view-ring-2nd))) + ;; Record another one, with format. + (kmacro-set-format "=%d=") + (kmacro-tests-define-macro (vconcat "bar")) + ;; Execute the first one, mocked up to insert counter. + ;; Should get default format. + (kmacro-tests-should-insert "11" + (kmacro-tests-simulate-command '(kmacro-call-ring-2nd nil))) + ;; Now display the 2nd ring macro and check result. + (kmacro-tests-should-match-message "C-c x i C-u C-c x i" + (kmacro-view-ring-2nd))) + +(kmacro-tests-deftest kmacro-tests-fill-ring-and-rotate () + "Macro ring can shift one way, shift the other way, swap and pop." + (cl-letf ((kmacro-ring-max 4)) + ;; Record enough macros that the first one drops off the history. + (dotimes (n (1+ kmacro-ring-max)) + (kmacro-tests-define-macro (make-vector (1+ n) (+ ?a n)))) + ;; Cycle the ring and check that #2 comes up. + (kmacro-tests-should-match-message "2*b" + (kmacro-tests-simulate-command '(kmacro-cycle-ring-next nil))) + ;; Execute the current macro and check arguments. + (kmacro-tests-should-insert "bbbb" + (kmacro-call-macro 2 t)) + ;; Cycle the ring the other way; #5 expected. + (kmacro-tests-should-match-message "5*e" (kmacro-cycle-ring-previous nil)) + ;; Swapping the top two should give #4. + (kmacro-tests-should-match-message "4*d" (kmacro-swap-ring)) + ;; Delete the top and expect #5. + (kmacro-tests-should-match-message "5*e" (kmacro-delete-ring-head)))) + + +(kmacro-tests-deftest kmacro-tests-test-ring-commands-when-no-macros () + "Ring commands give appropriate message when no macros exist." + (dolist (cmd '((kmacro-cycle-ring-next nil) + (kmacro-cycle-ring-previous nil) + (kmacro-swap-ring) + (kmacro-delete-ring-head) + (kmacro-view-ring-2nd) + (kmacro-call-ring-2nd nil) + (kmacro-view-macro))) + (kmacro-tests-should-match-message "No keyboard macro defined" + (kmacro-tests-simulate-command cmd)))) + +(kmacro-tests-deftest kmacro-tests-repeat-on-last-key () + "Kmacro commands can be run in sequence without prefix keys." + (let* ((prefix (where-is-internal 'kmacro-keymap nil t)) + ;; Make a sequence of events to run. + ;; Comments are expected output of mock macros + ;; on the first and second run of the sequence (see below). + (events (mapcar #'kmacro-tests-get-kmacro-key + '(kmacro-end-or-call-macro-repeat ;c / b + kmacro-end-or-call-macro-repeat ;c / b + kmacro-call-ring-2nd-repeat ;b / a + kmacro-cycle-ring-next + kmacro-end-or-call-macro-repeat ;a / a + kmacro-cycle-ring-previous + kmacro-end-or-call-macro-repeat ;c / b + kmacro-delete-ring-head + kmacro-end-or-call-macro-repeat ;b / a + ))) + (kmacro-tests-macros (list [?a] [?b] [?c])) + ;; What we want kmacro to see as keyboard command sequence + (first-event (seq-concatenate + 'vector + prefix + (vector (kmacro-tests-get-kmacro-key + 'kmacro-end-or-call-macro-repeat))))) + (cl-letf + ;; standardize repeat options + ((kmacro-repeat-no-prefix t) + (kmacro-call-repeat-key t) + (kmacro-call-repeat-with-arg nil)) + ;; "Record" two macros + (dotimes (_n 2) + (kmacro-tests-simulate-command '(kmacro-start-macro nil)) + (kmacro-tests-simulate-command '(kmacro-end-macro nil))) + ;; Start recording #3 + (kmacro-tests-simulate-command '(kmacro-start-macro nil)) + + ;; Set up pending keyboard events and a fresh buffer + ;; kmacro-set-counter is not one of the repeating kmacro + ;; commands so it should end the sequence. + (let* ((end-key (kmacro-tests-get-kmacro-key 'kmacro-set-counter)) + (kmacro-tests-events (append events (list end-key)))) + (cl-letf (((symbol-function #'this-single-command-keys) + (lambda () first-event))) + (use-local-map kmacro-tests-keymap) + (kmacro-tests-should-insert "ccbacb" + ;; End #3 and launch loop to read events. + (kmacro-end-or-call-macro-repeat nil)))) + + ;; `kmacro-edit-macro-repeat' should also stop the sequence, + ;; so run it again with that at the end. + (let* ((end-key (kmacro-tests-get-kmacro-key 'kmacro-edit-macro-repeat)) + (kmacro-tests-events (append events (list end-key)))) + (cl-letf (((symbol-function #'edit-kbd-macro) #'ignore) + ((symbol-function #'this-single-command-keys) + (lambda () first-event))) + (use-local-map kmacro-tests-keymap) + (kmacro-tests-should-insert "bbbbbaaba" + (kmacro-end-or-call-macro-repeat 3))))))) + +(kmacro-tests-deftest kmacro-tests-repeat-view-and-run () + "Kmacro view cycles through ring and executes macro just viewed." + (let* ((prefix (where-is-internal 'kmacro-keymap nil t)) + (kmacro-tests-events + (mapcar #'kmacro-tests-get-kmacro-key + (append (make-list 5 'kmacro-view-macro-repeat) + '(kmacro-end-or-call-macro-repeat + kmacro-set-counter)))) + ;; Make kmacro see this as keyboard command sequence. + (first-event (seq-concatenate + 'vector + prefix + (vector (kmacro-tests-get-kmacro-key + 'kmacro-view-macro-repeat)))) + ;; Construct a regexp to match the messages which should be + ;; produced by repeated view-repeats. + (macros-regexp (apply #'concat + (mapcar (lambda (c) (format ".+%s\n" c)) + '("d" "c" "b" "a" "d" "c"))))) + (cl-letf ((kmacro-repeat-no-prefix t) + (kmacro-call-repeat-key t) + (kmacro-call-repeat-with-arg nil) + ((symbol-function #'this-single-command-keys) (lambda () + first-event))) + ;; "Record" some macros. + (dotimes (n 4) + (kmacro-tests-define-macro (make-vector 1 (+ ?a n)))) + + (use-local-map kmacro-tests-keymap) + ;; 6 views (the direct call plus the 5 in events) should + ;; cycle through the ring and get to the second-to-last + ;; macro defined. + (kmacro-tests-should-insert "c" + (kmacro-tests-should-match-message macros-regexp + (kmacro-tests-simulate-command '(kmacro-view-macro-repeat nil))))))) + +(kmacro-tests-deftest kmacro-tests-bind-to-key-when-recording () + "Bind to key doesn't bind a key during macro recording." + (cl-letf ((global-map global-map) + (saved-binding (key-binding "\C-a")) + (kmacro-tests-sequences (list "\C-a"))) + (kmacro-tests-simulate-command '(kmacro-start-macro 1)) + (kmacro-bind-to-key nil) + (should (eq saved-binding (key-binding "\C-a"))))) + +(kmacro-tests-deftest kmacro-tests-name-or-bind-to-key-when-no-macro () + "Bind to key, symbol or register fails when when no macro exists." + (should-error (kmacro-bind-to-key nil)) + (should-error (kmacro-name-last-macro 'kmacro-tests-symbol-for-test)) + (should-error (kmacro-to-register))) + +(kmacro-tests-deftest kmacro-tests-bind-to-key-bad-key-sequence () + "Bind to key fails to bind to ^G." + (let ((global-map global-map) + (saved-binding (key-binding "\C-g")) + (kmacro-tests-sequences (list "\C-g"))) + (kmacro-tests-define-macro [1]) + (kmacro-bind-to-key nil) + (should (eq saved-binding (key-binding "\C-g"))))) + +(kmacro-tests-deftest kmacro-tests-bind-to-key-with-key-sequence-in-use () + "Bind to key respects yes-or-no-p when given already bound key sequence." + (kmacro-tests-define-macro (vconcat "abaab")) + (let ((global-map global-map) + (map (make-sparse-keymap)) + (kmacro-tests-sequences (make-list 2 "\C-hi"))) + (define-key map "\C-hi" 'info) + (use-local-map map) + ;; Try the command with yes-or-no-p set up to say no. + (cl-letf (((symbol-function #'yes-or-no-p) + (lambda (prompt) + (should (string-match-p "info" prompt)) + (should (string-match-p "C-h i" prompt)) + nil))) + (kmacro-bind-to-key nil)) + + (should (equal (where-is-internal 'info nil t) + (vconcat "\C-hi"))) + ;; Try it again with yes. + (cl-letf (((symbol-function #' yes-or-no-p) + (lambda (_prompt) t))) + (kmacro-bind-to-key nil)) + + (should-not (equal (where-is-internal 'info global-map t) + (vconcat "\C-hi"))) + (use-local-map nil) + (kmacro-tests-should-insert "abaab" + (funcall (key-binding "\C-hi"))))) + +(kmacro-tests-deftest kmacro-tests-kmacro-bind-to-single-key () + "Bind to key uses C-x C-k A when asked to bind to A." + (let ((global-map global-map) + (kmacro-tests-macros (list (string-to-vector "\C-cxi")))) + (use-local-map kmacro-tests-keymap) + + ;; Record a macro with counter and format set. + (kmacro-set-format "<%d>") + (kmacro-tests-simulate-command '(kmacro-start-macro-or-insert-counter 5)) + (kmacro-tests-simulate-command '(kmacro-end-macro nil)) + + (let ((kmacro-tests-sequences (list "A"))) + (kmacro-bind-to-key nil)) + + ;; Record a second macro with different counter and format. + (kmacro-set-format "%d") + (kmacro-tests-define-macro [2]) + + ;; Check the bound key and run it and verify correct counter + ;; and format. + (should (equal (string-to-vector "\C-cxi") + (car (kmacro-extract-lambda + (key-binding "\C-x\C-kA"))))) + (kmacro-tests-should-insert "<5>" + (funcall (key-binding "\C-x\C-kA"))))) + +(kmacro-tests-deftest kmacro-tests-name-last-macro-unable-to-bind () + "Name last macro won't bind to symbol which is already bound." + (kmacro-tests-define-macro [1]) + ;; Set up a test symbol which looks like a function. + (setplist 'kmacro-tests-symbol-for-test nil) + (fset 'kmacro-tests-symbol-for-test #'ignore) + (should-error (kmacro-name-last-macro 'kmacro-tests-symbol-for-test)) + ;; The empty string symbol also can't be bound. + (should-error (kmacro-name-last-macro (make-symbol "")))) + +(kmacro-tests-deftest kmacro-tests-name-last-macro-bind-and-rebind () + "Name last macro can rebind a symbol it binds." + ;; Make sure our symbol is unbound. + (when (fboundp 'kmacro-tests-symbol-for-test) + (fmakunbound 'kmacro-tests-symbol-for-test)) + (setplist 'kmacro-tests-symbol-for-test nil) + ;; Make two macros and bind them to the same symbol. + (dotimes (i 2) + (kmacro-tests-define-macro (make-vector (1+ i) (+ ?a i))) + (kmacro-name-last-macro 'kmacro-tests-symbol-for-test) + (should (fboundp 'kmacro-tests-symbol-for-test))) + + ;; Now run the function bound to the symbol. Result should be the + ;; second macro. + (kmacro-tests-should-insert "bb" + (kmacro-tests-simulate-command '(kmacro-tests-symbol-for-test)))) + +(kmacro-tests-deftest kmacro-tests-store-in-register () + "Macro can be stored in and retrieved from a register." + (use-local-map kmacro-tests-keymap) + ;; Save and restore register 200 so we can use it for the test. + (let ((saved-reg-contents (get-register 200))) + (unwind-protect + (progn + ;; Define a macro, and save it to a register. + (kmacro-tests-define-macro (vconcat "a\C-a\C-cxu")) + (kmacro-to-register 200) + ;; Then make a new different macro. + (kmacro-tests-define-macro (vconcat "bb\C-a\C-cxu")) + ;; When called from the register, result should be first macro. + (kmacro-tests-should-insert "AAA" + (kmacro-tests-simulate-command '(jump-to-register 200 3) 3)) + (kmacro-tests-should-insert "a C-a C-c x u" + (kmacro-tests-simulate-command '(insert-register 200 t) '(4)))) + (set-register 200 saved-reg-contents)))) + +(kmacro-tests-deftest kmacro-tests-step-edit-act () + "Step-edit steps-through a macro with act and act-repeat." + (kmacro-tests-run-step-edit "he\C-u2lo" + :events (make-list 6 'act) + :result "hello" + :macro-result "he\C-u2lo") + + (kmacro-tests-run-step-edit "f\C-aoo\C-abar" + :events (make-list 5 'act-repeat) + :result "baroof" + :macro-result "f\C-aoo\C-abar")) + +(kmacro-tests-deftest kmacro-tests-step-edit-skip () + "Step-editing can skip parts of macro." + (kmacro-tests-run-step-edit "ofoofff" + :events '(skip skip-keep skip-keep skip-keep + skip-rest) + :result "" + :macro-result "foo")) + +(kmacro-tests-deftest kmacro-tests-step-edit-quit () + "Quit while step-editing leaves macro unchanged." + (kmacro-tests-run-step-edit "bar" + :events '(help insert skip help quit) + :sequences '("f" "o" "o" "\C-j") + :result "foo" + :macro-result "bar")) + +(kmacro-tests-deftest kmacro-tests-step-insert () + "Step edit can insert in macro." + (kmacro-tests-run-step-edit "fbazbop" + :events '(insert act insert-1 act-repeat) + :sequences '("o" "o" "\C-a" "\C-j" "\C-e") + :result "foobazbop" + :macro-result "oo\C-af\C-ebazbop")) + +(kmacro-tests-deftest kmacro-tests-step-edit-replace-digit-argument () + "Step-edit replace can replace a numeric argument in a macro. +This is a regression for item 1 in Bug#24991." + (:expected-result :failed) + (kmacro-tests-run-step-edit "\C-u3b\C-a\C-cxu" + :events '(act replace automatic) + :sequences '("8" "x" "\C-j") + :result "XXXXXXXX" + :macro-result "\C-u8x\C-a\C-cxu")) + +(kmacro-tests-deftest kmacro-tests-step-edit-replace () + "Step-edit replace and replace-1 can replace parts of a macro." + (kmacro-tests-run-step-edit "a\C-a\C-cxu" + :events '(act act replace) + :sequences '("b" "c" "\C-j") + :result "bca" + :macro-result "a\C-abc") + (kmacro-tests-run-step-edit "a\C-a\C-cxucd" + :events '(act replace-1 automatic) + :sequences '("b") + :result "abcd" + :macro-result "ab\C-cxucd") + (kmacro-tests-run-step-edit "by" + :events '(act replace) + :sequences '("a" "r" "\C-j") + :result "bar" + :macro-result "bar")) + +(kmacro-tests-deftest kmacro-tests-step-edit-append () + "Step edit append inserts after point, and append-end inserts at end." + (kmacro-tests-run-step-edit "f-b" + :events '(append append-end) + :sequences '("o" "o" "\C-j" "a" "r" "\C-j") + :result "foo-bar" + :macro-result "foo-bar") + (kmacro-tests-run-step-edit "x" + :events '(append) + :sequences '("\C-a" "\C-cxu" "\C-e" "y" "\C-j") + :result "Xy" + :macro-result "x\C-a\C-cxu\C-ey")) + +(kmacro-tests-deftest kmacro-tests-append-end-at-end-appends () + "Append-end when already at end of macro appends to end of macro. +This is a regression for item 2 in Bug#24991." + (:expected-result :failed) + (kmacro-tests-run-step-edit "x" + :events '(append-end) + :sequences '("\C-a" "\C-cxu" "\C-e" "y" "\C-j") + :result "Xy" + :macro-result "x\C-a\C-cxu\C-ey")) + + +(kmacro-tests-deftest kmacro-tests-step-edit-skip-entire () + "Skipping a whole macro in step-edit leaves macro unchanged. +This is a regression for item 3 in Bug#24991." + (:expected-result :failed) + (kmacro-tests-run-step-edit "xyzzy" + :events '(skip-rest) + :result "" + :macro-result "xyzzy")) + +(kmacro-tests-deftest kmacro-tests-step-edit-step-through-negative-argument () + "Step edit works on macros using negative universal argument. +This is a regression for item 4 in Bug#24991." + (:expected-result :failed) + (kmacro-tests-run-step-edit "boo\C-u-\C-cu" + :events '(act-repeat automatic) + :result "BOO" + :macro-result "boo\C-u-\C-cd")) + +(kmacro-tests-deftest kmacro-tests-step-edit-with-quoted-insert () + "Stepping through a macro that uses quoted insert leaves macro unchanged. +This is a regression for item 5 in Bug#24991." + (:expected-result :failed) + (let ((read-quoted-char-radix 8)) + (kmacro-tests-run-step-edit "\C-cxq17051i there" + :events '(act automatic) + :result "ḩi there" + :macro-result "\C-cxq17051i there") + (kmacro-tests-run-step-edit "g\C-cxq17051i" + :events '(act insert-1 automatic) + :sequences '("-") + :result "g-ḩi" + :macro-result "g-\C-cxq17051i"))) + +(kmacro-tests-deftest kmacro-tests-step-edit-can-replace-meta-keys () + "Replacing C-w with M-w produces the expected result. +This is a regression for item 7 in Bug#24991." + (:expected-result :failed) + (kmacro-tests-run-step-edit "abc\C-b\C-b\C-SPC\C-f\C-w\C-e\C-y" + :events '(act-repeat act-repeat + act-repeat act-repeat + replace automatic) + :sequences '("\M-w" "\C-j") + :result "abcb" + :macro-result "abc\C-b\C-b\C-SPC\C-f\M-w\C-e\C-y") + (kmacro-tests-should-insert "abcb" (kmacro-call-macro nil))) + +(kmacro-tests-deftest kmacro-tests-step-edit-ignores-qr-map-commands () + "Unimplemented commands from `query-replace-map' are ignored." + (kmacro-tests-run-step-edit "yep" + :events '(edit-replacement + act-and-show act-and-exit + delete-and-edit + recenter backup + scroll-up scroll-down + scroll-other-window + scroll-other-window-down + exit-prefix + act act act) + :result "yep" + :macro-result "yep")) + +(kmacro-tests-deftest + kmacro-tests-step-edit-edits-macro-with-extended-command () + "Step-editing a macro which uses the minibuffer can change the macro." + (let ((mac (vconcat [?\M-x] "eval-expression" '[return] + "(insert-char (+ ?a \C-e" [?1] "))" '[return])) + (mac-after (vconcat [?\M-x] "eval-expression" '[return] + "(insert-char (+ ?a \C-e" [?2] "))" '[return]))) + + (kmacro-tests-run-step-edit mac + :events '(act act-repeat + act act-repeat act + replace-1 act-repeat act) + :sequences '("2") + :result "c" + :macro-result mac-after))) + +(kmacro-tests-deftest kmacro-tests-step-edit-step-through-isearch () + "Step-editing can edit a macro which uses `isearch-backward' (Bug#22488)." + (:expected-result :failed) + (let ((mac (vconcat "test Input" '[return] + [?\C-r] "inp" '[return] "\C-cxu")) + (mac-after (vconcat "test input" '[return] + [?\C-r] "inp" '[return] "\C-cd"))) + + (kmacro-tests-run-step-edit mac + :events '(act-repeat act act + act-repeat act + replace-1) + :sequences '("\C-cd") + :result "test input\n" + :macro-result mac-after))) + +(kmacro-tests-deftest kmacro-tests-step-edit-cleans-up-hook () + "Step-editing properly cleans up `post-command-hook.' (Bug #18708)" + (:expected-result :failed) + (let (post-command-hook) + (setq-local post-command-hook '(t)) + (kmacro-tests-run-step-edit "x" + :events '(act) + :result "x" + :macro-result "x") + (kmacro-tests-simulate-command '(beginning-of-line)))) + +(cl-defun kmacro-tests-run-step-edit + (macro &key events sequences result macro-result) + "Set up and run a test of `kmacro-step-edit-macro'. + +Run `kmacro-step-edit-macro' with MACRO defined as a keyboard macro +and `read-event' and `read-key-sequence' set up to return items from +EVENTS and SEQUENCES respectively. SEQUENCES may be nil, but +EVENTS should not be. EVENTS should be a list of symbols bound +in `kmacro-step-edit-map' or `query-replace' map, and this function +will do the keymap lookup for you. SEQUENCES should contain +return values for `read-key-sequence'. + +Before running the macro, the current buffer will be erased. +RESULT is the string that should be inserted during the +step-editing process, and MACRO-RESULT is the expected value of +`last-kbd-macro' after the editing is complete." + + (let* ((kmacro-tests-events (mapcar #'kmacro-tests-get-kmacro-step-edit-key events)) + (kmacro-tests-sequences sequences)) + + (kmacro-tests-define-macro (string-to-vector macro)) + (use-local-map kmacro-tests-keymap) + (erase-buffer) + (kmacro-step-edit-macro) + (when result + (should (equal result (buffer-string)))) + (when macro-result + (should (equal last-kbd-macro (string-to-vector macro-result)))))) + +;;; Utilities: + +(defun kmacro-tests-simulate-command (command &optional arg) + "Call `ert-simulate-command' after setting `current-prefix-arg'. +Sets `current-prefix-arg' to ARG if it is non-nil, otherwise to +the second element of COMMAND, before executing COMMAND using +`ert-simulate-command'." + (let ((current-prefix-arg (or arg (cadr command)))) + (ert-simulate-command command))) + +(defun kmacro-tests-define-macro (mac) + "Define MAC as a keyboard macro using kmacro commands." + (push mac kmacro-tests-macros) + (kmacro-tests-simulate-command '(kmacro-start-macro nil)) + (should defining-kbd-macro) + (kmacro-tests-simulate-command '(kmacro-end-macro nil)) + (should (equal mac last-kbd-macro))) + +(defun kmacro-tests-get-kmacro-key (sym) + "Look up kmacro command SYM in kmacro's keymap. +Return the integer key value found." + (aref (where-is-internal sym kmacro-keymap t) 0)) + +(defun kmacro-tests-get-kmacro-step-edit-key (sym) + "Return the first key bound to SYM in `kmacro-step-edit-map'." + (let ((where (aref (where-is-internal sym kmacro-step-edit-map t) 0))) + (if (consp where) + (car where) + where))) + +(provide 'kmacro-tests) + +;;; kmacro-tests.el ends here commit c8f91b168b0955e1e21acbf291171af1f70725ec Author: Eli Zaretskii Date: Sat Feb 4 13:49:55 2017 +0200 Fix autorevert-tests on MS-Windows * test/lisp/autorevert-tests.el (auto-revert-test02-auto-revert-deleted-file): Don't check that auto-revert-use-notify was reset to nil on w32. diff --git a/test/lisp/autorevert-tests.el b/test/lisp/autorevert-tests.el index c082ba9563..c6f103321c 100644 --- a/test/lisp/autorevert-tests.el +++ b/test/lisp/autorevert-tests.el @@ -190,7 +190,9 @@ This expects `auto-revert--messages' to be bound by ;; notification should be disabled, falling back to ;; polling. (should (string-match "any text" (buffer-string))) - (should-not auto-revert-use-notify) + ;; With w32notify, the 'stopped' events are not sent. + (or (eq file-notify--library 'w32notify) + (should-not auto-revert-use-notify)) ;; Once the file has been recreated, the buffer shall be ;; reverted. commit ef3d8d6f7226e570209e913d2754e828d0cb121c Author: Gemini Lasswell Date: Sat Feb 4 13:36:43 2017 +0200 New macro 'ert-with-message-capture' * lisp/emacs-lisp/ert-x.el (ert-with-message-capture): New macro. (Bug#25158) * test/lisp/autorevert-tests.el (auto-revert--wait-for-revert) (auto-revert-test00-auto-revert-mode) (auto-revert-test01-auto-revert-several-files) (auto-revert-test02-auto-revert-deleted-file) (auto-revert-test03-auto-revert-tail-mode) (auto-revert-test04-auto-revert-mode-dired): * test/lisp/filenotify-tests.el (file-notify-test03-autorevert): Use ert-with-message-capture. diff --git a/lisp/emacs-lisp/ert-x.el b/lisp/emacs-lisp/ert-x.el index 8530253d5b..4cf9d9609e 100644 --- a/lisp/emacs-lisp/ert-x.el +++ b/lisp/emacs-lisp/ert-x.el @@ -285,6 +285,30 @@ BUFFER defaults to current buffer. Does not modify BUFFER." (kill-buffer clone))))))) +(defmacro ert-with-message-capture (var &rest body) + "Execute BODY while collecting anything written with `message' in VAR. + +Capture all messages produced by `message' when it is called from +Lisp, and concatenate them separated by newlines into one string. + +This is useful for separating the issuance of messages by the +code under test from the behavior of the *Messages* buffer." + (declare (debug (symbolp body)) + (indent 1)) + (let ((g-advice (cl-gensym))) + `(let* ((,var "") + (,g-advice (lambda (func &rest args) + (if (or (null args) (equal (car args) "")) + (apply func args) + (let ((msg (apply #'format-message args))) + (setq ,var (concat ,var msg "\n")) + (funcall func "%s" msg)))))) + (advice-add 'message :around ,g-advice) + (unwind-protect + (progn ,@body) + (advice-remove 'message ,g-advice))))) + + (provide 'ert-x) ;;; ert-x.el ends here diff --git a/test/lisp/autorevert-tests.el b/test/lisp/autorevert-tests.el index aea855ae02..c082ba9563 100644 --- a/test/lisp/autorevert-tests.el +++ b/test/lisp/autorevert-tests.el @@ -24,24 +24,29 @@ ;;; Code: (require 'ert) +(require 'ert-x) (require 'autorevert) (setq auto-revert-notify-exclude-dir-regexp "nothing-to-be-excluded" auto-revert-stop-on-user-input nil) (defconst auto-revert--timeout 10 - "Time to wait until a message appears in the *Messages* buffer.") + "Time to wait for a message.") + +(defvar auto-revert--messages nil + "Used to collect messages issued during a section of a test.") (defun auto-revert--wait-for-revert (buffer) - "Wait until the *Messages* buffer reports reversion of BUFFER." + "Wait until a message reports reversion of BUFFER. +This expects `auto-revert--messages' to be bound by +`ert-with-message-capture' before calling." (with-timeout (auto-revert--timeout nil) - (with-current-buffer "*Messages*" - (while - (null (string-match - (format-message "Reverting buffer `%s'." (buffer-name buffer)) - (buffer-string))) - (if (with-current-buffer buffer auto-revert-use-notify) - (read-event nil nil 0.1) - (sleep-for 0.1)))))) + (while + (null (string-match + (format-message "Reverting buffer `%s'." (buffer-name buffer)) + auto-revert--messages)) + (if (with-current-buffer buffer auto-revert-use-notify) + (read-event nil nil 0.1) + (sleep-for 0.1))))) (ert-deftest auto-revert-test00-auto-revert-mode () "Check autorevert for a file." @@ -51,41 +56,38 @@ buf) (unwind-protect (progn - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (write-region "any text" nil tmpfile nil 'no-message) + (write-region "any text" nil tmpfile nil 'no-message) (setq buf (find-file-noselect tmpfile)) - (with-current-buffer buf - (should (string-equal (buffer-string) "any text")) - ;; `buffer-stale--default-function' checks for - ;; `verify-visited-file-modtime'. We must ensure that it - ;; returns nil. - (sleep-for 1) - (auto-revert-mode 1) - (should auto-revert-mode) + (with-current-buffer buf + (ert-with-message-capture auto-revert--messages + (should (string-equal (buffer-string) "any text")) + ;; `buffer-stale--default-function' checks for + ;; `verify-visited-file-modtime'. We must ensure that it + ;; returns nil. + (sleep-for 1) + (auto-revert-mode 1) + (should auto-revert-mode) - ;; Modify file. We wait for a second, in order to have - ;; another timestamp. - (sleep-for 1) - (write-region "another text" nil tmpfile nil 'no-message) + ;; Modify file. We wait for a second, in order to have + ;; another timestamp. + (sleep-for 1) + (write-region "another text" nil tmpfile nil 'no-message) - ;; Check, that the buffer has been reverted. - (auto-revert--wait-for-revert buf) + ;; Check, that the buffer has been reverted. + (auto-revert--wait-for-revert buf)) (should (string-match "another text" (buffer-string))) ;; When the buffer is modified, it shall not be reverted. - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (set-buffer-modified-p t) - (sleep-for 1) - (write-region "any text" nil tmpfile nil 'no-message) + (ert-with-message-capture auto-revert--messages + (set-buffer-modified-p t) + (sleep-for 1) + (write-region "any text" nil tmpfile nil 'no-message) - ;; Check, that the buffer hasn't been reverted. - (auto-revert--wait-for-revert buf) + ;; Check, that the buffer hasn't been reverted. + (auto-revert--wait-for-revert buf)) (should-not (string-match "any text" (buffer-string))))) ;; Exit. - (with-current-buffer "*Messages*" (widen)) (ignore-errors (with-current-buffer buf (set-buffer-modified-p nil)) (kill-buffer buf)) @@ -106,13 +108,11 @@ (make-temp-file (expand-file-name "auto-revert-test" tmpdir1))) buf1 buf2) (unwind-protect - (progn - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (write-region "any text" nil tmpfile1 nil 'no-message) - (setq buf1 (find-file-noselect tmpfile1)) - (write-region "any text" nil tmpfile2 nil 'no-message) - (setq buf2 (find-file-noselect tmpfile2)) + (ert-with-message-capture auto-revert--messages + (write-region "any text" nil tmpfile1 nil 'no-message) + (setq buf1 (find-file-noselect tmpfile1)) + (write-region "any text" nil tmpfile2 nil 'no-message) + (setq buf2 (find-file-noselect tmpfile2)) (dolist (buf (list buf1 buf2)) (with-current-buffer buf @@ -148,7 +148,6 @@ (should (string-match "another text" (buffer-string)))))) ;; Exit. - (with-current-buffer "*Messages*" (widen)) (ignore-errors (dolist (buf (list buf1 buf2)) (with-current-buffer buf (set-buffer-modified-p nil)) @@ -165,8 +164,6 @@ buf) (unwind-protect (progn - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) (write-region "any text" nil tmpfile nil 'no-message) (setq buf (find-file-noselect tmpfile)) (with-current-buffer buf @@ -184,42 +181,36 @@ 'before-revert-hook (lambda () (delete-file buffer-file-name)) nil t) - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (sleep-for 1) - (write-region "another text" nil tmpfile nil 'no-message) - ;; Check, that the buffer hasn't been reverted. File - ;; notification should be disabled, falling back to - ;; polling. - (auto-revert--wait-for-revert buf) + (ert-with-message-capture auto-revert--messages + (sleep-for 1) + (write-region "another text" nil tmpfile nil 'no-message) + (auto-revert--wait-for-revert buf)) + ;; Check, that the buffer hasn't been reverted. File + ;; notification should be disabled, falling back to + ;; polling. (should (string-match "any text" (buffer-string))) (should-not auto-revert-use-notify) ;; Once the file has been recreated, the buffer shall be ;; reverted. (kill-local-variable 'before-revert-hook) - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (sleep-for 1) - (write-region "another text" nil tmpfile nil 'no-message) - - ;; Check, that the buffer has been reverted. - (auto-revert--wait-for-revert buf) + (ert-with-message-capture auto-revert--messages + (sleep-for 1) + (write-region "another text" nil tmpfile nil 'no-message) + (auto-revert--wait-for-revert buf)) + ;; Check, that the buffer has been reverted. (should (string-match "another text" (buffer-string))) ;; An empty file shall still be reverted. - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (sleep-for 1) - (write-region "" nil tmpfile nil 'no-message) - - ;; Check, that the buffer has been reverted. - (auto-revert--wait-for-revert buf) + (ert-with-message-capture auto-revert--messages + (sleep-for 1) + (write-region "" nil tmpfile nil 'no-message) + (auto-revert--wait-for-revert buf)) + ;; Check, that the buffer has been reverted. (should (string-equal "" (buffer-string))))) ;; Exit. - (with-current-buffer "*Messages*" (widen)) (ignore-errors (with-current-buffer buf (set-buffer-modified-p nil)) (kill-buffer buf)) @@ -232,9 +223,7 @@ (let ((tmpfile (make-temp-file "auto-revert-test")) buf) (unwind-protect - (progn - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) + (ert-with-message-capture auto-revert--messages (write-region "any text" nil tmpfile nil 'no-message) (setq buf (find-file-noselect tmpfile)) (with-current-buffer buf @@ -259,7 +248,6 @@ (string-match "modified text\nanother text" (buffer-string))))) ;; Exit. - (with-current-buffer "*Messages*" (widen)) (ignore-errors (kill-buffer buf)) (ignore-errors (delete-file tmpfile))))) @@ -283,33 +271,29 @@ (should (string-match name (substring-no-properties (buffer-string)))) - ;; Delete file. We wait for a second, in order to have - ;; another timestamp. - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (sleep-for 1) - (delete-file tmpfile) - - ;; Check, that the buffer has been reverted. - (auto-revert--wait-for-revert buf) + (ert-with-message-capture auto-revert--messages + ;; Delete file. We wait for a second, in order to have + ;; another timestamp. + (sleep-for 1) + (delete-file tmpfile) + (auto-revert--wait-for-revert buf)) + ;; Check, that the buffer has been reverted. (should-not (string-match name (substring-no-properties (buffer-string)))) - ;; Make dired buffer modified. Check, that the buffer has - ;; been still reverted. - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (set-buffer-modified-p t) - (sleep-for 1) - (write-region "any text" nil tmpfile nil 'no-message) + (ert-with-message-capture auto-revert--messages + ;; Make dired buffer modified. Check, that the buffer has + ;; been still reverted. + (set-buffer-modified-p t) + (sleep-for 1) + (write-region "any text" nil tmpfile nil 'no-message) - ;; Check, that the buffer has been reverted. - (auto-revert--wait-for-revert buf) + (auto-revert--wait-for-revert buf)) + ;; Check, that the buffer has been reverted. (should (string-match name (substring-no-properties (buffer-string)))))) ;; Exit. - (with-current-buffer "*Messages*" (widen)) (ignore-errors (with-current-buffer buf (set-buffer-modified-p nil)) (kill-buffer buf)) diff --git a/test/lisp/filenotify-tests.el b/test/lisp/filenotify-tests.el index db7f55e8fc..27434bcef2 100644 --- a/test/lisp/filenotify-tests.el +++ b/test/lisp/filenotify-tests.el @@ -36,6 +36,7 @@ ;;; Code: (require 'ert) +(require 'ert-x) (require 'filenotify) (require 'tramp) @@ -703,21 +704,19 @@ delivered." (should auto-revert-notify-watch-descriptor) ;; Modify file. We wait for a second, in order to have - ;; another timestamp. - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (sleep-for 1) - (write-region - "another text" nil file-notify--test-tmpfile nil 'no-message) - - ;; Check, that the buffer has been reverted. - (with-current-buffer (get-buffer-create "*Messages*") - (file-notify--wait-for-events - timeout - (string-match + ;; another timestamp. + (ert-with-message-capture captured-messages + (sleep-for 1) + (write-region + "another text" nil file-notify--test-tmpfile nil 'no-message) + + ;; Check, that the buffer has been reverted. + (file-notify--wait-for-events + timeout + (string-match (format-message "Reverting buffer `%s'." (buffer-name buf)) - (buffer-string)))) - (should (string-match "another text" (buffer-string))) + captured-messages)) + (should (string-match "another text" (buffer-string)))) ;; Stop file notification. Autorevert shall still work via polling. (file-notify-rm-watch auto-revert-notify-watch-descriptor) @@ -728,27 +727,24 @@ delivered." ;; Modify file. We wait for two seconds, in order to ;; have another timestamp. One second seems to be too - ;; short. - (with-current-buffer (get-buffer-create "*Messages*") - (narrow-to-region (point-max) (point-max))) - (sleep-for 2) - (write-region - "foo bla" nil file-notify--test-tmpfile nil 'no-message) - - ;; Check, that the buffer has been reverted. - (with-current-buffer (get-buffer-create "*Messages*") - (file-notify--wait-for-events - timeout - (string-match - (format-message "Reverting buffer `%s'." (buffer-name buf)) - (buffer-string)))) - (should (string-match "foo bla" (buffer-string)))) + ;; short. + (ert-with-message-capture captured-messages + (sleep-for 2) + (write-region + "foo bla" nil file-notify--test-tmpfile nil 'no-message) + + ;; Check, that the buffer has been reverted. + (file-notify--wait-for-events + timeout + (string-match + (format-message "Reverting buffer `%s'." (buffer-name buf)) + captured-messages)) + (should (string-match "foo bla" (buffer-string))))) ;; The environment shall be cleaned up. (file-notify--test-cleanup-p)) ;; Cleanup. - (with-current-buffer "*Messages*" (widen)) (ignore-errors (kill-buffer buf)) (file-notify--test-cleanup)))) commit 8ba27b7ce2f4a98e3c14fe752042c60fd7576fef Author: Gemini Lasswell Date: Sat Feb 4 13:18:29 2017 +0200 Avoid invalid read syntax errors due to 'ert-with-test-buffer' * lisp/emacs-lisp/ert-x.el (ert-with-test-buffer): Fix the 'declare' form. (Bug#24722) diff --git a/lisp/emacs-lisp/ert-x.el b/lisp/emacs-lisp/ert-x.el index 7d99cb3027..8530253d5b 100644 --- a/lisp/emacs-lisp/ert-x.el +++ b/lisp/emacs-lisp/ert-x.el @@ -97,7 +97,7 @@ To be used in ERT tests. If BODY finishes successfully, the test buffer is killed; if there is an error, the test buffer is kept around on error for further inspection. Its name is derived from the name of the test and the result of NAME-FORM." - (declare (debug ((form) body)) + (declare (debug ((":name" form) body)) (indent 1)) `(ert--call-with-test-buffer ,name-form (lambda () ,@body))) commit a46a61904de6cc57e6a740a3006f48023859a1b3 Author: Eli Zaretskii Date: Sat Feb 4 13:12:14 2017 +0200 Fix a syntax error when evaluating pcase.el under Edebug * lisp/emacs-lisp/pcase.el (pcase-MACRO): Replace def-edebug-spec with an explicit 'put' form. Suggested by Gemini Lasswell . (Bug#24717) diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el index 54678c5f32..46a5eedd15 100644 --- a/lisp/emacs-lisp/pcase.el +++ b/lisp/emacs-lisp/pcase.el @@ -89,7 +89,8 @@ (functionp &rest form) sexp)) -(def-edebug-spec pcase-MACRO pcase--edebug-match-macro) +;; See bug#24717 +(put 'pcase-MACRO 'edebug-form-spec 'pcase--edebug-match-macro) ;; Only called from edebug. (declare-function get-edebug-spec "edebug" (symbol)) commit cc84a405f3d3eb99ac8d53721715bbd812cf9772 Author: Eli Zaretskii Date: Sat Feb 4 12:59:41 2017 +0200 ; * lisp/files.el (save-some-buffers-default-predicate): Add :version. diff --git a/lisp/files.el b/lisp/files.el index 03d6df4c03..b7d104853c 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -5139,7 +5139,8 @@ Before and after saving the buffer, this function runs This allows you to stop `save-some-buffers' from asking about certain files that you'd usually rather not save." :group 'auto-save - :type 'function) + :type 'function + :version "26.1") (defun save-some-buffers (&optional arg pred) "Save some modified file-visiting buffers. Asks user about each one. commit 78f841d6db77f8b72d6d7d221af26efb956ab6cb Author: Gemini Lasswell Date: Sat Feb 4 12:56:19 2017 +0200 Change edebug-max-depth from defconst to defcustom * lisp/emacs-lisp/edebug.el (edebug-max-depth): Add defcustom. (Bug#24713) * etc/NEWS: Mention edebug-max-depth. * doc/lispref/edebug.texi (Checking Whether to Stop): Mention edebug-max-depth and index it. Add cross-references for max-lisp-eval-depth and max-specpdl-size. Co-authored-by: Eli Zaretskii diff --git a/doc/lispref/edebug.texi b/doc/lispref/edebug.texi index f6f73ea894..da72c9b700 100644 --- a/doc/lispref/edebug.texi +++ b/doc/lispref/edebug.texi @@ -979,9 +979,13 @@ program. @itemize @bullet @item -@code{max-lisp-eval-depth} and @code{max-specpdl-size} are both -increased to reduce Edebug's impact on the stack. You could, however, -still run out of stack space when using Edebug. +@vindex edebug-max-depth +@code{max-lisp-eval-depth} (@pxref{Eval}) and @code{max-specpdl-size} +(@pxref{Local Variables}) are both increased to reduce Edebug's impact +on the stack. You could, however, still run out of stack space when +using Edebug. You can also enlarge the value of +@code{edebug-max-depth} if Edebug reaches the limit of recursion depth +instrumenting code that contains very large quoted lists. @item The state of keyboard macro execution is saved and restored. While diff --git a/etc/NEWS b/etc/NEWS index 270f8803d5..cbf2b70c82 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -464,6 +464,11 @@ viewing HTML files and the like. breakpoint (e.g. with "f" and "o") by customizing the new option 'edebug-sit-on-break'. ++++ +*** New customizable option 'edebug-max-depth' +This allows to enlarge the maximum recursion depth when instrumenting +code. + ** Eshell *** 'eshell-input-filter's value is now a named function diff --git a/lisp/emacs-lisp/edebug.el b/lisp/emacs-lisp/edebug.el index db54d1eeb2..ec0f08de35 100644 --- a/lisp/emacs-lisp/edebug.el +++ b/lisp/emacs-lisp/edebug.el @@ -112,6 +112,18 @@ and some not, use `def-edebug-spec' to specify an `edebug-form-spec'." :type 'boolean :group 'edebug) +(defcustom edebug-max-depth 150 + "Maximum recursion depth when instrumenting code. +This limit is intended to stop recursion if an Edebug specification +contains an infinite loop. When Edebug is instrumenting code +containing very large quoted lists, it may reach this limit and give +the error message \"Too deep - perhaps infinite loop in spec?\". +Make this limit larger to countermand that, but you may also need to +increase `max-lisp-eval-depth' and `max-specpdl-size'." + :type 'integer + :group 'edebug + :version "26.1") + (defcustom edebug-save-windows t "If non-nil, Edebug saves and restores the window configuration. That takes some time, so if your program does not care what happens to @@ -1452,7 +1464,6 @@ expressions; a `progn' form will be returned enclosing these forms." (defvar edebug-after-dotted-spec nil) (defvar edebug-matching-depth 0) ;; initial value -(defconst edebug-max-depth 150) ;; maximum number of matching recursions. ;;; Failure to match commit c71b718be86bdda7b51c8ea0da30aa896a7833fe Author: Eli Zaretskii Date: Sat Feb 4 12:02:55 2017 +0200 Support options with embedded whitespace in 'dired-listing-switches' * lisp/dired.el (dired-listing-switches): Document how to quote options with embedded whitespace. * lisp/files.el (insert-directory): Use split-string-and-unquote to support dired-listing-switches that specify command-line options with embedded spaces. (Bug#25485) diff --git a/lisp/dired.el b/lisp/dired.el index 350f6a7d2e..2733372eb7 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -59,6 +59,10 @@ May contain all other options that don't contradict `-l'; may contain even `F', `b', `i' and `s'. See also the variable `dired-ls-F-marks-symlinks' concerning the `F' switch. +Options that include embedded whitespace must be quoted +like this: \\\"--option=value with spaces\\\"; you can use +`combine-and-quote-strings' to produce the correct quoting of +each option. On systems such as MS-DOS and MS-Windows, which use `ls' emulation in Lisp, some of the `ls' switches are not supported; see the doc string of `insert-directory' in `ls-lisp.el' for more details." diff --git a/lisp/files.el b/lisp/files.el index 2833ec5c12..03d6df4c03 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -6582,7 +6582,7 @@ normally equivalent short `-D' option is just passed on to (unless (equal switches "") ;; Split the switches at any spaces so we can ;; pass separate options as separate args. - (split-string switches))) + (split-string-and-unquote switches))) ;; Avoid lossage if FILE starts with `-'. '("--") (progn commit 331eb6c915a4a12a3a1034615f68cd4dc4bd7e32 Author: Gemini Lasswell Date: Sat Feb 4 11:43:50 2017 +0200 Add tests for lisp/emacs-lisp/testcover.el * test/lisp/emacs-lisp/testcover-tests.el: New file. * test/lisp/emacs-lisp/testcover-resources/testcases.el: New file. Co-authored-by: Noam Postavsky diff --git a/test/lisp/emacs-lisp/testcover-resources/testcases.el b/test/lisp/emacs-lisp/testcover-resources/testcases.el new file mode 100644 index 0000000000..1eb791a993 --- /dev/null +++ b/test/lisp/emacs-lisp/testcover-resources/testcases.el @@ -0,0 +1,493 @@ +;;;; testcases.el -- Test cases for testcover-tests.el + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Author: Gemini Lasswell + +;; This file is part of GNU Emacs. + +;; 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: + +;; * This file should not be loaded directly. It is meant to be read +;; by `testcover-tests-build-test-cases'. +;; +;; * Test cases begin with ;; ==== name ====. The symbol name between +;; the ===='s is used to create the name of the test. +;; +;; * Following the beginning comment place the test docstring and +;; any tags or keywords for ERT. These will be spliced into the +;; ert-deftest for the test. +;; +;; * To separate the above from the test case code, use another +;; comment: ;; ==== +;; +;; * These special comments should start at the beginning of a line. +;; +;; * `testcover-tests-skeleton' will prompt you for a test name and +;; insert the special comments. +;; +;; * The test case code should be annotated with %%% at the end of +;; each form where a tan splotch is expected, and !!! at the end +;; of each form where a red mark is expected. +;; +;; * If Testcover is working correctly on your code sample, using +;; `testcover-tests-markup-region' and +;; `testcover-tests-unmarkup-region' can make creating test cases +;; easier. + +;;; Code: +;;; Test Cases: + +;; ==== constants-bug-25316 ==== +"Testcover doesn't splotch constants." +:expected-result :failed +;; ==== +(defconst testcover-testcase-const "apples") +(defun testcover-testcase-zero () 0) +(defun testcover-testcase-list-consts () + (list + emacs-version 10 + "hello" + `(a b c ,testcover-testcase-const) + '(1 2 3) + testcover-testcase-const + (testcover-testcase-zero) + nil)) + +(defun testcover-testcase-add-to-const-list (arg) + (cons arg%%% (testcover-testcase-list-consts))%%%) + +(should (equal (testcover-testcase-add-to-const-list 'a) + `(a ,emacs-version 10 "hello" (a b c "apples") (1 2 3) + "apples" 0 nil))) + +;; ==== customize-defcustom-bug-25326 ==== +"Testcover doesn't prevent testing of defcustom values." +:expected-result :failed +;; ==== +(defgroup testcover-testcase nil + "Test case for testcover" + :group 'lisp + :prefix "testcover-testcase-" + :version "26.0") +(defcustom testcover-testcase-flag t + "Test value used by testcover-tests.el" + :type 'boolean + :group 'testcover-testcase) +(defun testcover-testcase-get-flag () + testcover-testcase-flag) + +(testcover-testcase-get-flag) +(setq testcover-testcase-flag (not testcover-testcase-flag)) +(testcover-testcase-get-flag) + +;; ==== no-returns ==== +"Testcover doesn't splotch functions which don't return." +;; ==== +(defun testcover-testcase-play-ball (retval) + (catch 'ball + (throw 'ball retval%%%))%%%) ; catch gets marked but not throw + +(defun testcover-testcase-not-my-favorite-error-message () + (signal 'wrong-type-argument (list 'consp nil))) + +(should (testcover-testcase-play-ball t)) +(condition-case nil + (testcover-testcase-not-my-favorite-error-message) + (error nil)) + +;; ==== noreturn-symbol ==== +"Wrapping a form with noreturn prevents splotching." +;; ==== +(defun testcover-testcase-cancel (spacecraft) + (error "no destination for %s" spacecraft)) +(defun testcover-testcase-launch (spacecraft planet) + (if (null planet) + (noreturn (testcover-testcase-cancel spacecraft%%%)) + (list spacecraft%%% planet%%%)%%%)%%%) +(defun testcover-testcase-launch-2 (spacecraft planet) + (if (null planet%%%)%%% + (testcover-testcase-cancel spacecraft%%%)!!! + (list spacecraft!!! planet!!!)!!!)!!!) +(should (equal (testcover-testcase-launch "Curiosity" "Mars") '("Curiosity" "Mars"))) +(condition-case err + (testcover-testcase-launch "Voyager" nil) + (error err)) +(condition-case err + (testcover-testcase-launch-2 "Voyager II" nil) + (error err)) + +(should-error (testcover-testcase-launch "Voyager" nil)) +(should-error (testcover-testcase-launch-2 "Voyager II" nil)) + +;; ==== 1-value-symbol-bug-25316 ==== +"Wrapping a form with 1value prevents splotching." +:expected-result :failed +;; ==== +(defun testcover-testcase-always-zero (num) + (- num%%% num%%%)%%%) +(defun testcover-testcase-still-always-zero (num) + (1value (- num%%% num%%% (- num%%% num%%%)%%%))) +(defun testcover-testcase-never-called (num) + (1value (/ num!!! num!!!)!!!)!!!) +(should (eql 0 (testcover-testcase-always-zero 3))) +(should (eql 0 (testcover-testcase-still-always-zero 5))) + +;; ==== dotimes-dolist ==== +"Dolist and dotimes with a 1valued return value are 1valued." +;; ==== +(defun testcover-testcase-do-over (things) + (dolist (thing things%%%) + (list thing)) + (dolist (thing things%%% 42) + (list thing)) + (dolist (thing things%%% things%%%) + (list thing))%%%) +(defun testcover-testcase-do-more (count) + (dotimes (num count%%%) + (+ num num)) + (dotimes (num count%%% count%%%) + (+ num num))%%% + (dotimes (num count%%% 0) + (+ num num))) +(should (equal '(a b c) (testcover-testcase-do-over '(a b c)))) +(should (eql 0 (testcover-testcase-do-more 2))) + +;; ==== let-last-form ==== +"A let form is 1valued if its last form is 1valued." +;; ==== +(defun testcover-testcase-double (num) + (let ((double (* num%%% 2)%%%)) + double%%%)%%%) +(defun testcover-testcase-nullbody-let (num) + (let* ((square (* num%%% num%%%)%%%) + (double (* 2 num%%%)%%%)))) +(defun testcover-testcase-answer () + (let ((num 100)) + 42)) +(should-not (testcover-testcase-nullbody-let 3)) +(should (eql (testcover-testcase-answer) 42)) +(should (eql (testcover-testcase-double 10) 20)) + +;; ==== if-with-1value-clauses ==== +"An if is 1valued if both then and else are 1valued." +;; ==== +(defun testcover-testcase-describe (val) + (if (zerop val%%%)%%% + "a number" + "a different number")) +(defun testcover-testcase-describe-2 (val) + (if (zerop val) + "zero" + "not zero")) +(defun testcover-testcase-describe-3 (val) + (if (zerop val%%%)%%% + "zero" + (format "%d" val%%%)%%%)%%%) +(should (equal (testcover-testcase-describe 0) "a number")) +(should (equal (testcover-testcase-describe-2 0) "zero")) +(should (equal (testcover-testcase-describe-2 1) "not zero")) +(should (equal (testcover-testcase-describe-3 1) "1")) + +;; ==== cond-with-1value-clauses ==== +"A cond form is marked 1valued if all clauses are 1valued." +;; ==== +(defun testcover-testcase-cond (num) + (cond + ((eql num%%% 0)%%% 'a) + ((eql num%%% 1)%%% 'b) + ((eql num!!! 2)!!! 'c))) +(defun testcover-testcase-cond-2 (num) + (cond + ((eql num%%% 0)%%% (cons 'a 0)!!!) + ((eql num%%% 1)%%% 'b))%%%) +(should (eql (testcover-testcase-cond 1) 'b)) +(should (eql (testcover-testcase-cond-2 1) 'b)) + +;; ==== condition-case-with-1value-components ==== +"A condition-case is marked 1valued if its body and handlers are." +;; ==== +(defun testcover-testcase-cc (arg) + (condition-case nil + (if (null arg%%%)%%% + (error "foo") + "0")!!! + (error nil))) +(should-not (testcover-testcase-cc nil)) + +;; ==== quotes-within-backquotes-bug-25316 ==== +"Forms to instrument are found within quotes within backquotes." +:expected-result :failed +;; ==== +(defun testcover-testcase-make-list () + (list 'defun 'defvar)) +(defmacro testcover-testcase-bq-macro (arg) + (declare (debug t)) + `(memq ,arg%%% '(defconst ,@(testcover-testcase-make-list)))%%%) +(defun testcover-testcase-use-bq-macro (arg) + (testcover-testcase-bq-macro arg%%%)%%%) +(should (equal '(defun defvar) (testcover-testcase-use-bq-macro 'defun))) + +;; ==== progn-functions ==== +"Some forms are 1value if their last argument is 1value." +;; ==== +(defun testcover-testcase-one (arg) + (progn + (setq arg (1- arg%%%)%%%)%%%)%%% + (progn + (setq arg (1+ arg%%%)%%%)%%% + 1)) + +(should (eql 1 (testcover-testcase-one 0))) +;; ==== prog1-functions ==== +"Some forms are 1value if their first argument is 1value." +;; ==== +(defun testcover-testcase-unwinder (arg) + (unwind-protect + (if ( > arg%%% 0)%%% + 1 + 0) + (format "unwinding %s!" arg%%%)%%%)) +(defun testcover-testcase-divider (arg) + (unwind-protect + (/ 100 arg%%%)%%% + (format "unwinding! %s" arg%%%)%%%)%%%) + +(should (eq 0 (testcover-testcase-unwinder 0))) +(should (eq 1 (testcover-testcase-divider 100))) + +;; ==== compose-functions ==== +"Some functions are 1value if all their arguments are 1value." +;; ==== +(defconst testcover-testcase-count 3) +(defun testcover-testcase-number () + (+ 1 testcover-testcase-count)) +(defun testcover-testcase-more () + (+ 1 (testcover-testcase-number) testcover-testcase-count)) + +(should (equal (testcover-testcase-more) 8)) + +;; ==== apply-quoted-symbol ==== +"Apply with a quoted function symbol treated as 1value if function is." +;; ==== +(defun testcover-testcase-numlist (flag) + (if flag%%% + '(1 2 3) + '(4 5 6))) +(defun testcover-testcase-sum (flag) + (apply '+ (testcover-testcase-numlist flag%%%))) +(defun testcover-testcase-label () + (apply 'message "edebug uses: %s %s" (list 1 2)!!!)!!!) + +(should (equal 6 (testcover-testcase-sum t))) + +;; ==== backquote-1value-bug-24509 ==== +"Commas within backquotes are recognized as non-1value." +:expected-result :failed +;; ==== +(defmacro testcover-testcase-lambda (&rest body) + `(lambda () ,@body)) + +(defun testcover-testcase-example () + (let ((lambda-1 (testcover-testcase-lambda (format "lambda-%d" 1))%%%) + (lambda-2 (testcover-testcase-lambda (format "lambda-%d" 2))%%%)) + (concat (funcall lambda-1%%%)%%% " " + (funcall lambda-2%%%)%%%)%%%)%%%) + +(defmacro testcover-testcase-message-symbol (name) + `(message "%s" ',name)) + +(defun testcover-testcase-example-2 () + (concat + (testcover-testcase-message-symbol foo)%%% + (testcover-testcase-message-symbol bar)%%%)%%%) + +(should (equal "lambda-1 lambda-2" (testcover-testcase-example))) +(should (equal "foobar" (testcover-testcase-example-2))) + +;; ==== pcase-bug-24688 ==== +"Testcover copes with condition-case within backquoted list." +:expected-result :failed +;; ==== +(defun testcover-testcase-pcase (form) + (pcase form%%% + (`(condition-case ,var ,protected-form . ,handlers) + (list var%%% protected-form%%% handlers%%%)%%%) + (_ nil))%%%) + +(should (equal (testcover-testcase-pcase '(condition-case a + (/ 5 a) + (error 0))) + '(a (/ 5 a) ((error 0))))) + +;; ==== defun-in-backquote-bug-11307-and-24743 ==== +"Testcover handles defun forms within backquoted list." +:expected-result :failed +;; ==== +(defmacro testcover-testcase-defun (name &rest body) + (declare (debug (symbolp def-body))) + `(defun ,name () ,@body)) + +(testcover-testcase-defun foo (+ 1 2)) +(testcover-testcase-defun bar (+ 3 4)) +(should (eql (foo) 3)) +(should (eql (bar) 7)) + +;; ==== closure-1value-bug ==== +"Testcover does not mark closures as 1value." +:expected-result :failed +;; ==== +;; -*- lexical-binding:t -*- +(setq testcover-testcase-foo nil) +(setq testcover-testcase-bar 0) + +(defun testcover-testcase-baz (arg) + (setq testcover-testcase-foo + (lambda () (+ arg testcover-testcase-bar%%%)))) + +(testcover-testcase-baz 2) +(should (equal 2 (funcall testcover-testcase-foo))) +(testcover-testcase-baz 3) +(should (equal 3 (funcall testcover-testcase-foo))) + +;; ==== by-value-vs-by-reference-bug-25351 ==== +"An object created by a 1value expression may be modified by other code." +:expected-result :failed +;; ==== +(defun testcover-testcase-ab () + (list 'a 'b)) +(defun testcover-testcase-change-it (arg) + (setf (cadr arg%%%)%%% 'c)%%% + arg%%%) + +(should (equal (testcover-testcase-change-it (testcover-testcase-ab)) '(a c))) +(should (equal (testcover-testcase-ab) '(a b))) + +;; ==== 1value-error-test ==== +"Forms wrapped by `1value' should always return the same value." +;; ==== +(defun testcover-testcase-thing (arg) + (1value (list 1 arg 3))) + +(should (equal '(1 2 3) (testcover-testcase-thing 2))) +(should-error (testcover-testcase-thing 3)) + +;; ==== dotted-backquote ==== +"Testcover correctly instruments dotted backquoted lists." +;; ==== +(defun testcover-testcase-dotted-bq (flag extras) + (let* ((bq + `(a b c . ,(and flag extras%%%)))) + bq)) + +(should (equal '(a b c) (testcover-testcase-dotted-bq nil '(d e)))) +(should (equal '(a b c d e) (testcover-testcase-dotted-bq t '(d e)))) + +;; ==== backquoted-vector-bug-25316 ==== +"Testcover reinstruments within backquoted vectors." +:expected-result :failed +;; ==== +(defun testcover-testcase-vec (a b c) + `[,a%%% ,(list b%%% c%%%)%%%]%%%) + +(defun testcover-testcase-vec-in-list (d e f) + `([[,d%%% ,e%%%] ,f%%%])%%%) + +(defun testcover-testcase-vec-arg (num) + (list `[,num%%%]%%%)%%%) + +(should (equal [1 (2 3)] (testcover-testcase-vec 1 2 3))) +(should (equal '([[4 5] 6]) (testcover-testcase-vec-in-list 4 5 6))) +(should (equal '([100]) (testcover-testcase-vec-arg 100))) + +;; ==== vector-in-macro-spec-bug-25316 ==== +"Testcover reinstruments within vectors." +:expected-result :failed +;; ==== +(defmacro testcover-testcase-nth-case (arg vec) + (declare (indent 1) + (debug (form (vector &rest form)))) + `(eval (aref ,vec%%% ,arg%%%))%%%) + +(defun testcover-testcase-use-nth-case (choice val) + (testcover-testcase-nth-case choice + [(+ 1 val!!!)!!! + (- 1 val%%%)%%% + (* 7 val) + (/ 4 val!!!)!!!])) + +(should (eql 42 (testcover-testcase-use-nth-case 2 6))) +(should (eql 49 (testcover-testcase-use-nth-case 2 7))) +(should (eql 0 (testcover-testcase-use-nth-case 1 1 ))) + +;; ==== mapcar-is-not-compose ==== +"Mapcar with 1value arguments is not 1value." +:expected-result :failed +;; ==== +(defvar testcover-testcase-num 0) +(defun testcover-testcase-add-num (n) + (+ testcover-testcase-num n)) +(defun testcover-testcase-mapcar-sides () + (mapcar 'testcover-testcase-add-num '(1 2 3))) + +(setq testcover-testcase-num 1) +(should (equal (testcover-testcase-mapcar-sides) '(2 3 4))) +(setq testcover-testcase-num 2) +(should (equal (testcover-testcase-mapcar-sides) '(3 4 5))) + +;; ==== function-with-edebug-spec-bug-25316 ==== +"Functions can have edebug specs too. +See c-make-font-lock-search-function for an example in the Emacs +sources. The other issue is that it's ok to use quote in an +edebug spec, so testcover needs to cope with that." +:expected-result :failed +;; ==== +(defun testcover-testcase-make-function (forms) + `(lambda (flag) (if flag 0 ,@forms%%%))%%%) + +(def-edebug-spec testcover-testcase-make-function + (("quote" (&rest def-form)))) + +(defun testcover-testcase-thing () + (testcover-testcase-make-function '((+ 1 (+ 2 (+ 3 (+ 4 5))))))%%%) + +(defun testcover-testcase-use-thing () + (funcall (testcover-testcase-thing)%%% nil)%%%) + +(should (equal (testcover-testcase-use-thing) 15)) + +;; ==== backquoted-dotted-alist ==== +"Testcover can instrument a dotted alist constructed with backquote." +;; ==== +(defun testcover-testcase-make-alist (expr entries) + `((0 . ,expr%%%) . ,entries%%%)%%%) + +(should (equal (testcover-testcase-make-alist "foo" '((1 . "bar") (2 . "baz"))) + '((0 . "foo") (1 . "bar") (2 . "baz")))) + +;; ==== coverage-of-the-unknown-symbol-bug-25471 ==== +"Testcover correctly records coverage of code which uses `unknown'" +:expected-result :failed +;; ==== +(defun testcover-testcase-how-do-i-know-you (name) + (let ((val 'unknown)) + (when (equal name%%% "Bob")%%% + (setq val 'known)!!!) + val%%%)%%%) + +(should (eq (testcover-testcase-how-do-i-know-you "Liz") 'unknown)) + +;; testcases.el ends here. diff --git a/test/lisp/emacs-lisp/testcover-tests.el b/test/lisp/emacs-lisp/testcover-tests.el new file mode 100644 index 0000000000..d31379c3aa --- /dev/null +++ b/test/lisp/emacs-lisp/testcover-tests.el @@ -0,0 +1,186 @@ +;;; testcover-tests.el --- Testcover test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Author: Gemini Lasswell + +;; This file is part of GNU Emacs. + +;; 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: + +;; Testcover test suite. +;; * All the test cases are in testcover-resources/testcover-cases.el. +;; See that file for an explanation of the test case format. +;; * `testcover-tests-define-tests', which is run when this file is +;; loaded, reads testcover-resources/testcover-cases.el and defines +;; ERT tests for each test case. + +;;; Code: + +(require 'ert) +(require 'testcover) +(require 'skeleton) + +;; Use `eval-and-compile' around all these definitions because they're +;; used by the macro `testcover-tests-define-tests'. + +(eval-and-compile + (defvar testcover-tests-file-dir + (expand-file-name + "testcover-resources/" + (file-name-directory (or (bound-and-true-p byte-compile-current-file) + load-file-name + buffer-file-name))) + "Directory of the \"testcover-tests.el\" file.")) + +(eval-and-compile + (defvar testcover-tests-test-cases + (expand-file-name "testcases.el" testcover-tests-file-dir) + "File containing marked up code to instrument and check.")) + +;; Convert Testcover's overlays to plain text. + +(eval-and-compile + (defun testcover-tests-markup-region (beg end &rest optargs) + "Mark up test code within region between BEG and END. +Convert Testcover's tan and red splotches to %%% and !!! for +testcases.el. This can be used to create test cases if Testcover +is working correctly on a code sample. OPTARGS are optional +arguments for `testcover-start'." + (interactive "r") + (let ((tempfile (make-temp-file "testcover-tests-" nil ".el")) + (code (buffer-substring beg end)) + (marked-up-code)) + (unwind-protect + (progn + (with-temp-file tempfile + (insert code)) + (save-current-buffer + (let ((buf (find-file-noselect tempfile))) + (set-buffer buf) + (apply 'testcover-start (cons tempfile optargs)) + (testcover-mark-all buf) + (dolist (overlay (overlays-in (point-min) (point-max))) + (let ((ov-face (overlay-get overlay 'face))) + (goto-char (overlay-end overlay)) + (cond + ((eq ov-face 'testcover-nohits) (insert "!!!")) + ((eq ov-face 'testcover-1value) (insert "%%%")) + (t nil)))) + (setq marked-up-code (buffer-string))) + (set-buffer-modified-p nil))) + (ignore-errors (kill-buffer (find-file-noselect tempfile))) + (ignore-errors (delete-file tempfile))) + + ;; Now replace the original code with the marked up code. + (delete-region beg end) + (insert marked-up-code)))) + +(eval-and-compile + (defun testcover-tests-unmarkup-region (beg end) + "Remove the markup used in testcases.el between BEG and END." + (interactive "r") + (save-excursion + (save-restriction + (narrow-to-region beg end) + (goto-char (point-min)) + (while (re-search-forward "!!!\\|%%%" nil t) + (replace-match "")))))) + +(define-skeleton testcover-tests-skeleton + "Write a testcase for testcover-tests.el." + "Enter name of test: " + ";; ==== " str " ====\n" + "\"docstring\"\n" + ";; Directives for ERT should go here, if any.\n" + ";; ====\n" + ";; Replace this line with annotated test code.\n") + +;; Check a test case. + +(eval-and-compile + (defun testcover-tests-run-test-case (marked-up-code) + "Test the operation of Testcover on the string MARKED-UP-CODE." + (let ((tempfile (make-temp-file "testcover-tests-" nil ".el"))) + (unwind-protect + (progn + (with-temp-file tempfile + (insert marked-up-code)) + ;; Remove the marks and mark the code up again. The original + ;; and recreated versions should match. + (save-current-buffer + (set-buffer (find-file-noselect tempfile)) + ;; Fail the test if the debugger tries to become active, + ;; which will happen if Testcover's reinstrumentation + ;; leaves an edebug-enter in the code. This will also + ;; prevent debugging these tests using Edebug. + (cl-letf (((symbol-function #'edebug-enter) + (lambda (&rest _args) + (ert-fail + (concat "Debugger invoked during test run " + "(possible edebug-enter not replaced)"))))) + (dolist (byte-compile '(t nil)) + (testcover-tests-unmarkup-region (point-min) (point-max)) + (unwind-protect + (testcover-tests-markup-region (point-min) (point-max) byte-compile) + (set-buffer-modified-p nil)) + (should (string= marked-up-code + (buffer-string))))))) + (ignore-errors (kill-buffer (find-file-noselect tempfile))) + (ignore-errors (delete-file tempfile)))))) + +;; Convert test case file to ert-defmethod. + +(eval-and-compile + (defun testcover-tests-build-test-cases () + "Parse the test case file and return a list of ERT test definitions. +Construct and return a list of `ert-deftest' forms. See testcases.el +for documentation of the test definition format." + (let (results) + (with-temp-buffer + (insert-file-contents testcover-tests-test-cases) + (goto-char (point-min)) + (while (re-search-forward + (concat "^;; ==== \\([^ ]+?\\) ====\n" + "\\(\\(?:.*\n\\)*?\\)" + ";; ====\n" + "\\(\\(?:.*\n\\)*?\\)" + "\\(\\'\\|;; ====\\)") + nil t) + (let ((name (match-string 1)) + (splice (car (read-from-string + (format "(%s)" (match-string 2))))) + (code (match-string 3))) + (push + `(ert-deftest ,(intern (concat "testcover-tests-" name)) () + ,@splice + (testcover-tests-run-test-case ,code)) + results)) + (beginning-of-line))) + results))) + +;; Define all the tests. + +(defmacro testcover-tests-define-tests () + "Construct and define ERT test methods using the test case file." + (let* ((test-cases (testcover-tests-build-test-cases))) + `(progn ,@test-cases))) + +(testcover-tests-define-tests) + +(provide 'testcover-tests) + +;;; testcover-tests.el ends here