* bug#25068: 25.1; Edebug fails to instrument code which uses propertized strings
2017-02-04 11:26 ` Eli Zaretskii
@ 2017-02-04 17:39 ` Gemini Lasswell
2017-02-05 1:49 ` Gemini Lasswell
0 siblings, 1 reply; 5+ messages in thread
From: Gemini Lasswell @ 2017-02-04 17:39 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 25068
[-- Attachment #1: Type: text/plain, Size: 324 bytes --]
This could be fixed by updating the list in edebug-read-function of
characters that might possibly follow # to keep up with the changes to
read1 since the last time that list was updated (in 2001). But I think
it's better to remove the test entirely. If there is an invalid
character then read1 will report a syntax error.
[-- Attachment #2: 0001-Add-tests-for-lisp-kmacro.el.patch --]
[-- Type: text/plain, Size: 43156 bytes --]
From ad117828d954ddb8c37d134055d1a74b1344b078 Mon Sep 17 00:00:00 2001
From: gazally <gazally@users.noreply.github.com>
Date: Sun, 13 Nov 2016 08:02:38 -0800
Subject: [PATCH] Add tests for lisp/kmacro.el
* test/lisp/kmacro-tests.el: New file.
---
test/lisp/kmacro-tests.el | 907 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 907 insertions(+)
create mode 100644 test/lisp/kmacro-tests.el
diff --git a/test/lisp/kmacro-tests.el b/test/lisp/kmacro-tests.el
new file mode 100644
index 0000000..fbf9685
--- /dev/null
+++ b/test/lisp/kmacro-tests.el
@@ -0,0 +1,907 @@
+;;; kmacro-tests.el --- Tests for kmacro.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+
+;;; 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-with-message-capture (var &rest body)
+ "Execute BODY while collecting anything written with `message' in VAR."
+ (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)))))
+
+(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)))
+ `(kmacro-tests-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
--
2.10.1
^ permalink raw reply related [flat|nested] 5+ messages in thread