Subject: bug#25068: 25.1; Edebug fails to instrument code which uses propertized strings

From: Gemini Lasswell <[email-masked]>
Date: Sat, 04 Feb 2017 09:39:51 -0800

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.

>From ad117828d954ddb8c37d134055d1a74b1344b078 Mon Sep 17 00:00:00 2001
From: gazally <[email-masked]>
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