From: Gemini Lasswell <gazally@runbox.com>
To: 24939@debbugs.gnu.org
Subject: bug#24939: [PATCH] Add tests for lisp/kmacro.el
Date: Sun, 13 Nov 2016 13:23:51 -0800 [thread overview]
Message-ID: <m2vavr5b14.fsf@rainbow.local> (raw)
[-- Attachment #1: Type: text/plain, Size: 893 bytes --]
Hello,
There weren't any tests for kmacro.el, so I have written some.
Two things that may yet need to be done:
1. These tests make extensive use of two macros, called
kmacro-tests-should-call and kmacro-tests-should-not-call. They are
context-creating macros which add advice to named functions for the
duration of a test. I think that these two macros would be a useful
addition to ERT, and I'll submit that idea as a separate patch.
2. I found several minor bugs in the process of writing these, leading
to tests marked as :expected-result :failed. One is a way to create an
empty keyboard macro using the mouse and the rest are ways to get
kmacro-step-edit-macro to behave oddly. I haven't sent them to
bug-gnu-emacs yet. When I do so would it be better to send them
individually or put all the step-edit ones in one report?
My copyright assignment paperwork was finished as of Nov 2.
[-- Attachment #2: 0001-Add-tests-for-lisp-kmacro.el.patch --]
[-- Type: text/plain, Size: 58822 bytes --]
From 6dc518ac6f356a55593184ed6e1bafa5cd764623 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 | 1251 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1251 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..fae9cbc
--- /dev/null
+++ b/test/lisp/kmacro-tests.el
@@ -0,0 +1,1251 @@
+;;; 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 'edmacro)
+(require 'ert)
+(require 'ert-x)
+
+;;; Test fixtures:
+
+(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-e" . end-of-line)
+ ("\C-r" . isearch-backward)
+ ("\C-u" . universal-argument)
+ ("\M-x" . execute-extended-command)
+ ("\C-cd" . downcase-word)
+ ("\C-cxu" . upcase-word)
+ ("\C-cxq" . quoted-insert)))
+ (define-key map (car item) (cdr item)))
+
+ map)
+ "Keymap to use for testing keyboard macros.
+Used to obtain consistent results even if tests are run in an
+environment with rebound keys.")
+
+(defmacro kmacro-tests-with-kmacro-clean-slate (directions &rest body)
+ "Create a clean environment for a kmacro test to run in.
+Save the kmacro global variables and set them to reasonable
+default values. Temporarily bind the functions defined by
+macros.c and used by kmacro.el to mocked versions as requested by
+DIRECTIONS (see the docstring of `kmacro-tests-make-macros-c-rebindings'
+for details). Then execute BODY, and restore the variables and
+functions."
+ (declare (debug ((&rest (gate gv-place &optional form)) body)))
+ (let ((var-bindings
+ '(;; Macro customizations
+ (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)
+
+ ;; Macro recording state
+ (kbd-macro-termination-hook nil)
+ (defining-kbd-macro nil)
+ (executing-kbd-macro nil)
+ (executing-kbd-macro-index 0)
+ (last-kbd-macro nil)
+
+ ;; Macro history
+ (kmacro-ring nil)
+
+ ;; Macro counter
+ (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)))
+
+ ;; Rebindings for macros.c functions
+ (func-bindings (kmacro-tests-make-macros-c-rebindings directions)))
+
+ `(cl-letf* ,(append var-bindings func-bindings) ,@body)))
+
+(defvar kmacro-tests-stubs
+ '((start-kbd-macro . (lambda (_append _noexec)
+ (setq defining-kbd-macro t)))
+ (end-kbd-macro . (lambda (&optional _repeat _loopfunc)
+ (setq defining-kbd-macro nil)))
+ (call-last-kbd-macro . (lambda (_count loopfunc)
+ (and loopfunc (funcall loopfunc))))
+ (execute-kbd-macro . (lambda (_mac _count loopfunc)
+ (and loopfunc (funcall loopfunc)))))
+ "Stubs for functions from macros.c that can be used to test kmacro.el.")
+
+(defun kmacro-tests-make-macros-c-rebindings (directions)
+ "Create rebindings suitable for `cl-letf' for the functions in macros.c.
+DIRECTIONS is a list that may contain :use, :stub and the symbols
+for the four functions in macros.c which are used by
+kmacro.el. If any of the four functions is not in the list it
+will be rebound to a form which will cause a test failure if it
+is called. A function preceded by :stub will be bound to a mock
+with a minimal level of functionality, and one preceded by :use
+will be left unbound."
+ (let ((protected (mapcar #'car kmacro-tests-stubs))
+ stub-bindings safety-bindings func directive)
+ (while directions
+ (if (keywordp (car directions))
+ (setq directive (car directions))
+ (setq func (car directions))
+ (cond
+ ;; directions should only contain macros.c symbols or :use or :stub
+ ((or (not (keywordp directive))
+ (not (symbolp func))
+ (not (assoc func kmacro-tests-stubs)))
+ (message "rebindings syntax %s" directions))
+
+ ;; write a binding for a stubbed function
+ ((eq directive :stub)
+ (let ((binding
+ `((symbol-function #',func)
+ (lambda (&rest args)
+ (apply ,(alist-get func kmacro-tests-stubs) args)))))
+ (setq stub-bindings (cons binding stub-bindings))))
+
+ ;; Remove function from list that will be protected from
+ ;; unwanted calls
+ ((eq directive :use) (setq protected (delq func protected)))
+
+ (t (should-not directive))))
+ (setq directions (cdr directions)))
+
+ ;; Write bindings that will cause a test failure if any functions
+ ;; not named by :stub or :use are called.
+ (setq safety-bindings
+ (mapcar (lambda (func)
+ `((symbol-function #',func)
+ (lambda (&rest args) (should-not ',func))))
+ protected))
+ (append safety-bindings stub-bindings)))
+
+;;; Wrapper for ert-deftest to set up everthing a kmacro test needs
+
+(defmacro kmacro-tests-deftest (name _args docstring &rest keys-and-body)
+ "Set up clean context for 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. Directives for rebinding functions from
+macros.c may be placed in another list after ert's keywords and
+before the body. For example:
+
+ (kmacro-tests-deftest a-kmacro-test ()
+ \"docstring\"
+ (:tags '(:expensive-test) :expected-result :fail)
+ (:stub end-kbd-macro :use execute-kbd-macro)
+ (body-of-test))"
+
+ (declare (debug (name sexp stringp
+ (&rest keywordp sexp)
+ (&rest [keywordp sexp])
+ body))
+ (doc-string 3)
+ (indent 2))
+
+ (let* ((keys (when (and (listp (car keys-and-body))
+ (keywordp (caar keys-and-body))
+ (not (eq (caar keys-and-body) :use))
+ (not (eq (caar keys-and-body) :stub)))
+ (car keys-and-body)))
+ (directions-and-body (if keys (cdr keys-and-body)
+ keys-and-body))
+ (directions (when (and (listp (car directions-and-body))
+ (keywordp (caar directions-and-body)))
+ (car directions-and-body)))
+ (body (if directions (cdr directions-and-body)
+ directions-and-body)))
+ `(ert-deftest ,name ()
+ ,docstring ,@keys
+ (kmacro-tests-with-kmacro-clean-slate ,directions
+ ,@body))))
+
+(defmacro kmacro-tests-with-current-prefix-arg (form)
+ "Set `current-prefix-arg' temporarily while executing FORM.
+Use the second element of form as the argument. It will be
+evaluated twice."
+ (declare (debug (form)))
+ `(cl-letf ((current-prefix-arg ,(cadr form)))
+ ,form))
+
+;; Some more powerful expectations
+
+(defun kmacro-tests-arg-recorder ()
+ "Return a closure which will save its arguments for later examination.
+Helper for `kmacro-tests-should-call'. Create a lambda which
+saves the arguments given to it inside a closure. If you instead
+pass it :call-args, it will return the list of saved arguments."
+ (let (arglist)
+ (lambda (&rest args)
+ (if (eq (car args) :call-args)
+ arglist
+ (setq arglist (cons args arglist))))))
+
+(defmacro kmacro-tests-should-call (defs &rest body)
+ "Verify that the function(s) in DEFS are called by BODY.
+DEFS should be a list containing elements of the form:
+ (FUNC ARGCHECK WHERE FUNCTION)
+
+where FUNC is a symbol, ARGCHECK is either :once, :times followed
+by a value, or :check-args-with followed by a function value.
+WHERE and FUNCTION are optional and have the same meaning as in
+`advice-add'.
+
+Temporarily add advice to each FUNC in DEFS, including advice
+which records the arguments passed to FUNC (by reference not
+copy, relevant for destructive functions), execute BODY, and then
+depending on the ARGCHECK forms, verify that FUNC was either
+called once, a specified number of times, or that the function
+following :check-args-with returns a non-nil value when passed a
+list of all the arguments passed to FUNC (which will be in
+reverse order)."
+ (declare (debug ((&rest (gate fboundp
+ [&or ":once"
+ [":times" form]
+ [":check-args-with" function-form]]
+ &optional keywordp function-form))
+ body))
+ (indent 1))
+ (if (null defs)
+ `(progn ,@body)
+ (let* ((g-argrec (cl-gensym))
+ (g-advice (cl-gensym))
+ (def (car defs))
+ (func (car def))
+ (check-type (nth 1 def))
+ (advice-given (> (length def) 3))
+ (advice-keyword (car (last def 2)))
+ (advice-function (car (last def))))
+ ;; Add two pieces of advice to the function: the one provided in
+ ;; the definitions list, and another to record the arguments.
+ `(let ((,g-argrec (kmacro-tests-arg-recorder))
+ (,g-advice ,advice-function)) ; only evaluate lambdas once
+ (advice-add ',func :before ,g-argrec '((depth . -100)))
+ (unwind-protect
+ (progn
+ ,(when advice-given
+ `(advice-add ',func ,advice-keyword ,g-advice
+ '((depth . -99))))
+ (unwind-protect
+ (kmacro-tests-should-call ,(cdr defs) ,@body)
+ ,(when advice-given
+ `(advice-remove ',func ,g-advice))))
+ (advice-remove ',func ,g-argrec))
+ ;; Generate the after-execution argument list check.
+ ,(cond
+ ((eq check-type :once)
+ `(should (eql 1 (length (funcall ,g-argrec :call-args)))))
+ ((eq check-type :times)
+ `(should (eql ,(nth 2 def) (length (funcall ,g-argrec
+ :call-args)))))
+ ((eq check-type :check-args-with)
+ `(should (funcall ,(nth 2 def) (funcall ,g-argrec
+ :call-args)))))))))
+
+(defmacro kmacro-tests-should-not-call (func-or-funcs &rest body)
+ "Verify that a function or functions are not called during execution.
+FUNC-OR-FUNCS can either be a single function or a list of them.
+Temporarily override them them during the execution of BODY with advice
+containing `should-not' and a non-nil value. Use the function symbol
+as the non-nil value to make tracking down what went wrong a bit
+faster."
+ (declare (debug (&or [(&rest fboundp) body]
+ [fboundp body]))
+ (indent 1))
+ (let* ((funcs (if (consp func-or-funcs)
+ func-or-funcs
+ (list func-or-funcs)))
+ (defs (mapcar (lambda (f)
+ `(,f :times 0 :override
+ (lambda (&rest args) (should-not ',f))))
+ funcs)))
+ `(kmacro-tests-should-call ,defs
+ ,@body)))
+
+(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. As a side effect captures the
+symbols p_ and bsize_."
+ (declare (debug (stringp body))
+ (indent 1))
+ `(let ((p_ (point))
+ (bsize_ (buffer-size)))
+ ,@body
+ (should (equal (buffer-substring p_ (point)) ,value))
+ (should (equal (- (buffer-size) bsize_) (string-width ,value)))))
+
+(defmacro kmacro-tests-should-match-message (value &rest body)
+ "Verify that a message matching VALUE is issued while executing BODY.
+Execute BODY, then check for a regexp match between
+VALUE and any text written to *Messages* during the execution."
+ (declare (debug (stringp body))
+ (indent 1))
+ `(with-current-buffer (get-buffer-create "*Messages*")
+ (save-restriction
+ (narrow-to-region (point-max) (point-max))
+ ,@body
+ (should (string-match-p ,value (buffer-string))))))
+
+;; Tests begin here
+
+(kmacro-tests-deftest kmacro-tests-test-insert-counter-01-nil ()
+ "Insert counter adds one with nil arg."
+ (with-temp-buffer
+ (kmacro-tests-should-insert "0"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))
+ (kmacro-tests-should-insert "1"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))))
+
+(kmacro-tests-deftest kmacro-tests-test-insert-counter-02-int ()
+ "Insert counter increments by value of list argument."
+ (with-temp-buffer
+ (kmacro-tests-should-insert "0"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter 2)))
+ (kmacro-tests-should-insert "2"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter 3)))
+ (kmacro-tests-should-insert "5"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))))
+
+(kmacro-tests-deftest kmacro-tests-test-insert-counter-03-list ()
+ "Insert counter doesn't increment when given universal argument."
+ (with-temp-buffer
+ (kmacro-tests-should-insert "0"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter '(16))))
+ (kmacro-tests-should-insert "0"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter '(4))))))
+
+(kmacro-tests-deftest kmacro-tests-test-insert-counter-04-neg ()
+ "Insert counter decrements with '- prefix argument"
+ (ert-with-test-buffer (:name "")
+ (kmacro-tests-should-insert "0"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter '-))
+ )
+ (kmacro-tests-should-insert "-1"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))))
+
+(kmacro-tests-deftest kmacro-tests-test-start-format-counter ()
+ "Insert counter uses start value and format."
+ (ert-with-test-buffer (:name "start-format-counter")
+ (kmacro-tests-with-current-prefix-arg (kmacro-set-counter 10))
+ (kmacro-tests-should-insert "10"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))
+ (kmacro-tests-should-insert "11"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))
+ (kmacro-set-format "c=%s")
+ (kmacro-tests-with-current-prefix-arg (kmacro-set-counter 50))
+ (kmacro-tests-should-insert "c=50"
+ (kmacro-tests-with-current-prefix-arg (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."
+ ;; Verify kmacro-start-macro calls start-kbd-macro
+ (:stub start-kbd-macro)
+ (kmacro-tests-should-call
+ ((start-kbd-macro :once :before (lambda (append noexec)
+ (should-not append)
+ (should-not noexec))))
+ (kmacro-tests-with-current-prefix-arg (kmacro-start-macro nil)))
+ ;; And leaves macro in being-recorded state
+ (should defining-kbd-macro)
+ ;; And that it doesn't call it again
+ (kmacro-tests-should-not-call start-kbd-macro
+ (kmacro-tests-with-current-prefix-arg (kmacro-start-macro nil))))
+
+
+(kmacro-tests-deftest kmacro-tests-set-macro-counter-while-defining ()
+ "Use of the prefix arg with kmacro-start sets kmacro-counter."
+ (:stub start-kbd-macro)
+ ;; Give kmacro-start-macro an argument
+ (kmacro-tests-should-call
+ ((start-kbd-macro :once :before (lambda (append noexec)
+ (should-not append)
+ (should-not noexec))))
+ (kmacro-tests-with-current-prefix-arg (kmacro-start-macro 5)))
+ (should defining-kbd-macro)
+ ;; And verify that the counter is set to that value
+ (ert-with-test-buffer (:name "counter-while-defining")
+ (kmacro-tests-should-insert "5"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))
+ ;; change it while defining macro
+ (kmacro-tests-with-current-prefix-arg (kmacro-set-counter 1))
+ (kmacro-tests-should-insert "1"
+ (kmacro-tests-with-current-prefix-arg (kmacro-insert-counter nil)))
+ ;; using universal arg to to set counter should reset to starting value
+ (kmacro-tests-with-current-prefix-arg (kmacro-set-counter '(4)))
+ (kmacro-tests-should-insert "5"
+ (kmacro-tests-with-current-prefix-arg (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."
+ (:stub start-kbd-macro end-kbd-macro)
+ ;; start recording a macro
+ (kmacro-tests-should-call ((start-kbd-macro :once))
+ (kmacro-tests-with-current-prefix-arg
+ (kmacro-start-macro-or-insert-counter nil)))
+ ;; make sure we are still recording
+ (should defining-kbd-macro)
+ ;; called again should insert the counter
+ (ert-with-test-buffer (:name "start-insert-counter-appends")
+ (kmacro-tests-should-not-call start-kbd-macro
+ (kmacro-tests-should-insert "0"
+ (kmacro-tests-with-current-prefix-arg
+ (kmacro-start-macro-or-insert-counter nil)))))
+ ;; still recording
+ (should defining-kbd-macro)
+ ;; end recording, check that repeat count is passed
+ (kmacro-tests-should-call ((end-kbd-macro
+ :once :after
+ (lambda (repeat loopfunc)
+ (should (eql repeat 3))
+ (should (functionp loopfunc))
+ (setq last-kbd-macro
+ (string-to-vector "hello\C-a")))))
+ (kmacro-tests-with-current-prefix-arg (kmacro-end-or-call-macro 3)))
+ ;; now not recording
+ (should-not defining-kbd-macro)
+ ;; now append to the previous macro
+ (kmacro-tests-should-call
+ ((start-kbd-macro
+ :once :before (lambda (append noexec)
+ (should append)
+ (if kmacro-execute-before-append
+ (should noexec)
+ (should-not noexec)))))
+ (kmacro-tests-with-current-prefix-arg
+ (kmacro-start-macro-or-insert-counter '(16))))
+ (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."
+ (:stub call-last-kbd-macro)
+ ;; "record" two macros
+ (dotimes (i 2)
+ (kmacro-tests-mock-define-macro (make-vector (1+ i) i)))
+
+ ;; with no prefix arg, it should call the second macro
+ (kmacro-tests-should-call ((call-last-kbd-macro
+ :once :before
+ (lambda (count loopfunc)
+ (should (equal last-kbd-macro [1 1])))))
+ (kmacro-tests-with-current-prefix-arg (kmacro-end-or-call-macro nil)))
+
+ ;; universal arg should call the first one
+ (kmacro-tests-should-call ((execute-kbd-macro
+ :once :override
+ (lambda (macro count loopfunc)
+ (should (equal [0] macro))
+ (should (eql 1 count))
+ (should (functionp loopfunc)))))
+ (kmacro-tests-with-current-prefix-arg (kmacro-end-or-call-macro '(4)))))
+
+
+(kmacro-tests-deftest kmacro-tests-end-and-call-macro-tests ()
+ "Commands to end and call macro work under various conditions."
+ ;; this is currently failing because kmacro-end-call-mouse leaves an empty
+ ;; macro at the head of the ring.
+ (:expected-result :failed)
+ (:stub start-kbd-macro end-kbd-macro call-last-kbd-macro)
+ ;; test 2 very similar ways to do the same thing
+ (dolist (command '(kmacro-end-and-call-macro
+ kmacro-end-call-mouse))
+
+ (kmacro-tests-should-call
+ ((start-kbd-macro :once)
+ (end-kbd-macro :once :after
+ (lambda (repeat loopfunc)
+ (setq last-kbd-macro "")))
+ (call-last-kbd-macro
+ :once :before
+ (lambda (count loopfunc)
+ (should (eql (if (eq command 'kmacro-end-and-call-macro) 2)
+ count))))
+ ;; kmacro-end-call-mouse uses this
+ (mouse-set-point :check-args-with (lambda (_args) t)
+ :override #'ignore))
+ (kmacro-tests-with-current-prefix-arg (kmacro-start-macro nil))
+ ;; try the command while defining a macro
+ (cl-letf ((current-prefix-arg 2))
+ (ert-simulate-command `(,command 2))))
+
+ ;; 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 defining a macro
+ (kmacro-tests-should-call ((call-last-kbd-macro
+ :once :before
+ (lambda (count loopfunc)
+ (should (eql nil count)))))
+ (cl-letf ((current-prefix-arg nil))
+ (ert-simulate-command `(,command nil))))))
+
+(kmacro-tests-deftest kmacro-tests-call-macro-hint-and-repeat ()
+ "`kmacro-call-macro' gives hint in Messages and sets up repeat keymap.
+\(Bug#11817)"
+ (:stub start-kbd-macro end-kbd-macro call-last-kbd-macro)
+ (kmacro-tests-mock-define-macro [5])
+ (cl-letf ((kmacro-call-repeat-key t)
+ (kmacro-call-repeat-with-arg t)
+ (last-input-event ?e))
+ (let (exitfunc)
+
+ (message "") ; clear the echo area
+ (kmacro-tests-should-match-message "Type e to repeat macro"
+ (kmacro-tests-should-call
+ ((this-single-command-keys :once
+ :override (lambda () [?\C-x ?e]))
+ (call-last-kbd-macro :times 2
+ :before (lambda (count loopfunc)
+ (should (eql 3 count))))
+ (set-transient-map :times 2
+ :around (lambda (func &rest args)
+ (push (apply func args)
+ exitfunc)
+ (car exitfunc))))
+
+ (kmacro-call-macro 3)
+ ;; check that it set up for repeat, and run the repeat
+ (funcall (lookup-key overriding-terminal-local-map "e"))))
+ ;; get rid of the transient maps made by `kmacro-call-macro'
+ (while exitfunc
+ (funcall (pop exitfunc))))))
+
+(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)"
+ (ert-skip "Skipping due to Bug#24921 (an ERT bug)")
+ (:expected-result :failed)
+ (:stub start-kbd-macro end-kbd-macro :use call-last-kbd-macro)
+ (kmacro-tests-mock-define-macro (vconcat "foo" [return]
+ (where-is-internal
+ 'execute-extended-command nil t)
+ "kmacro-end-and-call-macro"))
+ (ert-with-test-buffer (:name "Bug#15126")
+ (switch-to-buffer (current-buffer))
+ (use-local-map kmacro-tests-keymap)
+ (ert-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."
+ (:stub start-kbd-macro end-kbd-macro execute-kbd-macro)
+ (kmacro-tests-should-call ((start-kbd-macro :once)
+ (end-kbd-macro
+ :once :after (lambda (repeat loopfunc)
+ (setq last-kbd-macro [1]))))
+ ;; Record one macro, with count
+ (kmacro-tests-with-current-prefix-arg (kmacro-start-macro 1))
+ (kmacro-tests-with-current-prefix-arg (kmacro-end-macro nil)))
+
+ ;; Check that execute and display do nothing with no 2nd macro
+ (kmacro-tests-should-not-call execute-kbd-macro
+ (kmacro-tests-with-current-prefix-arg (kmacro-call-ring-2nd nil)))
+ (kmacro-tests-should-match-message "Only one keyboard macro defined"
+ (kmacro-view-ring-2nd))
+
+ ;; Record another one, with format
+ (kmacro-set-format "=%d=")
+ (kmacro-tests-mock-define-macro [2 2])
+
+ ;; Execute the first one, mocked up to insert counter.
+ ;; Should get default format.
+ (kmacro-tests-should-call
+ ((execute-kbd-macro
+ :once :after
+ (lambda (mac count loopfunc)
+ (should (equal [1] mac))
+ (should (null count))
+ (kmacro-tests-with-current-prefix-arg
+ (kmacro-insert-counter nil))
+ (kmacro-tests-with-current-prefix-arg
+ (kmacro-insert-counter '(4))))))
+ (with-temp-buffer
+ (kmacro-tests-should-insert "11"
+ (kmacro-tests-with-current-prefix-arg (kmacro-call-ring-2nd nil)))))
+
+ ;; Now display and check that the [1] becomes C-a
+ (kmacro-tests-should-match-message "C-a" (kmacro-view-ring-2nd)))
+
+
+(kmacro-tests-deftest kmacro-tests-fill-ring-and-rotate ()
+ "Macro ring can do the hokey pokey, and shake it all about."
+ (:stub call-last-kbd-macro)
+ (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-mock-define-macro (make-vector (1+ n) (1+ n))))
+
+ ;; Cycle the ring and check that #2 comes up
+ (kmacro-tests-should-match-message "2*C-b" (kmacro-cycle-ring-next nil))
+
+ ;; Execute the current macro and check arguments
+ (kmacro-tests-should-call ((call-last-kbd-macro
+ :once :before
+ (lambda (count loopfunc)
+ (should (zerop count)))))
+ (kmacro-call-macro 0 t))
+
+ ;; Cycle the ring the other way; #5 expected
+ (kmacro-tests-should-match-message "5*C-e" (kmacro-cycle-ring-previous nil))
+ ;; Swapping the top two should give #4
+ (kmacro-tests-should-match-message "4*C-d" (kmacro-swap-ring))
+ ;; Delete the top and expect #5
+ (kmacro-tests-should-match-message "5*C-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"
+ (apply #'funcall cmd))))
+
+(kmacro-tests-deftest kmacro-tests-repeat-on-last-key ()
+ "Kmacro commands can be run in sequence without prefix keys."
+ (:stub start-kbd-macro end-kbd-macro
+ :use call-last-kbd-macro execute-kbd-macro)
+ ;; I'm attempting to make the test work even if keys have been
+ ;; rebound, but if this is failing try emacs -Q first.
+ (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
+ )))
+ ;; 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-mock-define-macro (make-vector 1 (+ ?a n))))
+ ;; Start recording #3
+ (kmacro-tests-should-call
+ ((start-kbd-macro :once))
+ (kmacro-tests-with-current-prefix-arg (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))
+ (seq1 (append events (list end-key))))
+ (kmacro-tests-should-call
+ ((end-kbd-macro :once :after
+ (lambda (repeat loopfunc)
+ (setq last-kbd-macro [?c])))
+ (call-last-kbd-macro :times 5)
+ (execute-kbd-macro :once)
+ (read-event :times (length seq1)
+ :around (kmacro-tests-make-mock-read-func seq1))
+ (this-single-command-keys :once :override (lambda ()
+ first-event)))
+ (ert-with-test-buffer (:name "first sequence")
+ (switch-to-buffer (current-buffer))
+ (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))
+ (switch-to-prev-buffer nil t))))
+
+ ;; 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))
+ (seq2 (append events (list end-key))))
+ (kmacro-tests-should-call
+ ((call-last-kbd-macro :times 6)
+ (execute-kbd-macro :once)
+ (edit-kbd-macro :once :override #'ignore)
+ (read-event :times (length seq2)
+ :around (kmacro-tests-make-mock-read-func seq2))
+ (this-single-command-keys :once :override (lambda ()
+ first-event)))
+ (ert-with-test-buffer (:name "second sequence")
+ (switch-to-buffer (current-buffer))
+ (use-local-map kmacro-tests-keymap)
+ (kmacro-tests-should-insert "bbbbbaaba"
+ (kmacro-end-or-call-macro-repeat 3))
+ (switch-to-prev-buffer nil t)))))))
+
+(kmacro-tests-deftest kmacro-tests-repeat-view-and-run ()
+ "Kmacro view cycles through ring and executes macro just viewed."
+ (:use execute-kbd-macro)
+ ;; I'm attempting to make the test work even if keys have been
+ ;; rebound, but if this is failing try emacs -Q first.
+ (let* ((prefix (where-is-internal 'kmacro-keymap nil t))
+ (events (mapcar #'kmacro-tests-get-kmacro-key
+ (append (make-list 5 'kmacro-view-macro-repeat)
+ '(kmacro-end-or-call-macro-repeat
+ kmacro-set-counter))))
+ ;; What we want kmacro to see as keyboard command sequence
+ (first-event (seq-concatenate
+ 'vector
+ prefix
+ (vector (kmacro-tests-get-kmacro-key
+ 'kmacro-view-macro-repeat))))
+ ;; Results of repeated view-repeats
+ (macros '("d" "c" "b" "a" "d" "c")))
+ (cl-letf ((kmacro-repeat-no-prefix t)
+ (kmacro-call-repeat-key t)
+ (kmacro-call-repeat-with-arg nil))
+ ;; "Record" some macros
+ (dotimes (n 4)
+ (kmacro-tests-mock-define-macro (make-vector 1 (+ ?a n))))
+
+ ;; Set up pending keyboard events and a fresh buffer
+ (ert-with-test-buffer (:name "view sequence")
+ (switch-to-buffer (current-buffer))
+ (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-call
+ ((execute-kbd-macro :once)
+ (read-event :times (length events)
+ :around (kmacro-tests-make-mock-read-func events))
+ (message :times 6
+ :after
+ (lambda (&rest _args)
+ (with-current-buffer
+ (get-buffer-create "*Messages*")
+ (should (equal
+ (car macros)
+ (buffer-substring (- (point-max) 2)
+ (- (point-max) 1))))
+ (setq macros (cdr macros)))))
+ (this-single-command-keys :once :override (lambda ()
+ first-event)))
+
+ (ert-simulate-command '(kmacro-view-macro-repeat nil))))
+ (switch-to-prev-buffer nil t)))))
+
+(kmacro-tests-deftest kmacro-tests-bind-to-key-when-recording ()
+ "Bind to key fails when recording a macro."
+ (:stub start-kbd-macro)
+ (kmacro-tests-should-call
+ ((start-kbd-macro :once))
+ (kmacro-tests-with-current-prefix-arg (kmacro-start-macro 1)))
+ (kmacro-tests-should-not-call read-key-sequence
+ (kmacro-bind-to-key nil)))
+
+(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."
+ (kmacro-tests-mock-define-macro [1])
+ (kmacro-tests-should-call ((read-key-sequence :once
+ :override (lambda (_s)
+ "\C-g")))
+ (kmacro-tests-should-not-call define-key (kmacro-bind-to-key nil))))
+
+(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-mock-define-macro [1])
+ (with-temp-buffer
+ (switch-to-buffer (current-buffer))
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-hi" 'info)
+ (use-local-map map))
+
+ (kmacro-tests-should-call
+ ((read-key-sequence
+ :times 2
+ :around (kmacro-tests-make-mock-read-func (make-list 2 "\C-hi"))))
+
+ ;; Try the command with yes-or-no-p set up to say no
+ (kmacro-tests-should-call ((yes-or-no-p
+ :once
+ :override
+ (lambda (prompt)
+ (should (string-match-p "info" prompt))
+ (should (string-match-p "C-h i" prompt))
+ nil)))
+ (kmacro-tests-should-not-call define-key
+ (kmacro-bind-to-key nil)))
+
+ ;; Try it again with yes
+ (kmacro-tests-should-call ((yes-or-no-p :once
+ :override (lambda (_prompt) t))
+ (define-key :once :override
+ (lambda (map keys l-form)
+ (should (eq map global-map))
+ (should (equal keys "\C-hi"))
+ (should (functionp l-form)))))
+ (kmacro-bind-to-key nil)))
+ (switch-to-prev-buffer nil t)))
+
+
+(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."
+ (:stub start-kbd-macro end-kbd-macro execute-kbd-macro)
+ ;; record a macro with counter and format set
+ (kmacro-set-format "<%d>")
+ (kmacro-tests-should-call ((start-kbd-macro :once)
+ (end-kbd-macro :once :after
+ (lambda (repeat loopfunc)
+ (setq last-kbd-macro [1]))))
+ (kmacro-tests-with-current-prefix-arg
+ (kmacro-start-macro-or-insert-counter 5))
+ (kmacro-tests-with-current-prefix-arg (kmacro-end-macro nil)))
+
+ ;; bind it to a key and save the lambda form it makes
+ (let ((prefix (where-is-internal 'kmacro-keymap nil t))
+ lambda-form)
+ (kmacro-tests-should-call ((read-key-sequence :once
+ :override (lambda (_s) "A"))
+ (define-key :once :override
+ (lambda (map seq lform)
+ (should (eq map global-map))
+ (should (equal seq (concat prefix "A")))
+ (setq lambda-form lform))))
+ (kmacro-bind-to-key nil))
+
+ ;; record a second macro with different counter and format
+ (kmacro-set-format "%d")
+ (kmacro-tests-mock-define-macro [2])
+
+ ;; Check the lambda form and run it and verify correct counter
+ ;; and format
+ (should (equal [1] (car (kmacro-extract-lambda lambda-form))))
+ (ert-with-test-buffer (:name "bind single key")
+ (kmacro-tests-should-insert "<5>"
+ (kmacro-tests-should-call ((execute-kbd-macro
+ :once :after
+ (lambda (mac count loopfunc)
+ (should (equal [1] mac))
+ (should-not count)
+ (kmacro-tests-with-current-prefix-arg
+ (kmacro-insert-counter nil)))))
+ (funcall lambda-form))))))
+
+(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-mock-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-mock-define-macro (make-vector (1+ i) (1+ 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, should be the second macro
+ (kmacro-tests-should-call ((execute-kbd-macro
+ :once :override
+ (lambda (mac _count _loopfunc)
+ (should (equal mac [2 2])))))
+ (funcall #'kmacro-tests-symbol-for-test)))
+
+(kmacro-tests-deftest kmacro-tests-store-in-register ()
+ "Macro can be stored in and retrieved from a register."
+ (kmacro-tests-mock-define-macro [1])
+ ;; save and restore register 200 so we can use it for the test
+ (let ((saved-reg-contents (get-register 200)))
+ (unwind-protect
+ (progn
+ (kmacro-to-register 200)
+ ;; then make a new different macro
+ (kmacro-tests-mock-define-macro [2 2])
+ (should (equal last-kbd-macro [2 2]))
+ ;; call from the register, should get first macro
+ (kmacro-tests-should-call ((call-last-kbd-macro
+ :once :override
+ (lambda (count _loopfunc)
+ (should (equal count 3))
+ (should (equal last-kbd-macro [1])))))
+ (cl-letf ((current-prefix-arg 3))
+ (jump-to-register 200 3)))
+ ;; check-that insert-register gives us human-readable macro
+ (ert-with-test-buffer (:name "register")
+ (cl-letf ((current-prefix-arg nil))
+ (insert-register 200 nil))
+ (should (equal (buffer-string) "C-a"))))
+ (set-register 200 saved-reg-contents))))
+
+(kmacro-tests-deftest kmacro-tests-step-edit-01 ()
+ "Step-edit act, skip, insert, quit."
+ (:use call-last-kbd-macro)
+
+ ;; Stepping through macro with 'act key
+ (kmacro-tests-run-step-edit "he\C-u2lo"
+ :events (make-list 6 'act)
+ :result "hello"
+ :macro-result "he\C-u2lo")
+
+ ;; Grouping similar commands using act-repeat
+ (kmacro-tests-run-step-edit "f\C-aoo\C-abar"
+ :events (make-list 5 'act-repeat)
+ :states '("" "f" "f" "oof" "oof")
+ :result "baroof"
+ :macro-result "f\C-aoo\C-abar")
+
+ ;; skipping parts of macro while editing
+ (kmacro-tests-run-step-edit "ofoofff"
+ :events '(skip skip-keep skip-keep skip-keep
+ skip-rest)
+ :result ""
+ :macro-result "foo")
+
+ ;; quitting 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")
+
+ ;; insert and insert-1 add to 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 numeric argument in macro."
+ (:expected-result :failed)
+ (:use call-last-kbd-macro)
+ ;; Replace numeric argument in macro
+ (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"
+ (:use call-last-kbd-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."
+ (:use call-last-kbd-macro)
+
+ (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."
+ (:expected-result :failed)
+ (:use call-last-kbd-macro)
+
+ (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 whole macro in step-edit leaves macro unchanged."
+ (:expected-result :failed)
+ (:use call-last-kbd-macro)
+
+ (kmacro-tests-run-step-edit "xyzzy"
+ :events '(skip-rest)
+ :result ""
+ :macro-result "xyzzy"))
+
+(kmacro-tests-deftest kmacro-tests-step-edit-with-quoted-insert ()
+ "Stepping through a macro that uses quoted insert leaves macro unchanged."
+ (:expected-result :failed)
+ (:use call-last-kbd-macro)
+ (cl-letf ((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-deftest kmacro-tests-step-edit-step-through-negative-argument ()
+ "Step edit works on macros using negative universal argument."
+ (:expected-result :failed)
+ (:use call-last-kbd-macro)
+ (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-can-insert-before-quoted-insert ()
+ "Step edit can insert before a quoted-insert in a macro."
+ (:expected-result :failed)
+ (:use call-last-kbd-macro)
+ (cl-letf ((read-quoted-char-radix 8))
+ (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-ignores-qr-map-commands ()
+ "Unimplemented commands from query-replace map are ignored."
+ (:use call-last-kbd-macro)
+ (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."
+ (:use call-last-kbd-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)
+ (:use call-last-kbd-macro)
+ (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)
+ (:use call-last-kbd-macro)
+ (let* ((mac "x")
+ (mac-after "x"))
+ (kmacro-tests-run-step-edit mac
+ :setup (lambda ()
+ (setq-local post-command-hook '(t)))
+ :teardown (lambda ()
+ (ert-simulate-command
+ '(beginning-of-line)))
+ :events '(act)
+ :result "x"
+ :macro-result mac-after)))
+
+
+(cl-defun kmacro-tests-run-step-edit
+ (macro &key events sequences setup states teardown result macro-result)
+ "Implement a sort of macro to test editing other macros.
+
+Run kmacro-step-edit-macro with MACRO defined as a keyboard macro
+and mock functions attached to read-event and read-key-sequence
+which will 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.
+
+During execution an ert test buffer will be current. SETUP, if
+provided, should be a function taking no arguments which will be
+called after the buffer for the test is created and before the
+macro runs. STATES may be nil or a list of strings, and if it is
+the latter, before each call to read-event, the contents of the
+buffer will be expected to match the strings in the
+list. TEARDOWN, if non-nil, should be a function taking no
+arguments which will be called after the macro is run and before
+the buffer is deleted.
+
+RESULT is the string that should be inserted in (the empty test)
+buffer during the step-editing process, and MACRO-RESULT is the
+expected value of last-kbd-macro after the editing is
+complete."
+
+ (let* ((event-keys (mapcar #'kmacro-tests-get-kmacro-step-edit-key events))
+ (mock-read-event (kmacro-tests-make-mock-read-func event-keys))
+ (current-state states))
+
+ (kmacro-tests-mock-define-macro (string-to-vector macro))
+ (ert-with-test-buffer (:name "run-step-edit")
+ (when setup
+ (funcall setup))
+ (switch-to-buffer (current-buffer))
+ (use-local-map kmacro-tests-keymap)
+ (kmacro-tests-should-call
+ ((call-last-kbd-macro :once)
+ (read-event :check-args-with
+ (lambda (arglists)
+ ;; extra calls may come from sit-for
+ (>= (length arglists) (length event-keys)))
+ :around
+ (lambda (&rest args)
+ (when states
+ (should (equal (car current-state) (buffer-string)))
+ (setq current-state (cdr current-state)))
+ (apply mock-read-event args)))
+ (read-key-sequence
+ :times (length sequences)
+ :around (kmacro-tests-make-mock-read-func sequences)))
+ (kmacro-step-edit-macro))
+ (when result
+ (should (equal result (buffer-string))))
+ (when macro-result
+ (should (equal last-kbd-macro (string-to-vector macro-result))))
+ (when teardown
+ (funcall teardown))
+ (switch-to-prev-buffer nil t))))
+
+;;; Mocks
+
+(defun kmacro-tests-mock-define-macro (mac)
+ "Mock-define a macro.
+Call into kmacro.el to start and end macro recording with the
+macros.c calls mocked to set `last-kbd-macro' to MAC. Does its own
+mocking of `start-kbd-macro' and `end-kbd-macro' so they don't have to
+be stubbed by the calling test."
+ (kmacro-tests-should-call
+ ((start-kbd-macro :once :override
+ (alist-get 'start-kbd-macro kmacro-tests-stubs))
+ (end-kbd-macro :once :override
+ (lambda (&rest args)
+ (apply
+ (alist-get 'end-kbd-macro kmacro-tests-stubs) args)
+ (setq last-kbd-macro mac))))
+ (kmacro-tests-with-current-prefix-arg (kmacro-start-macro nil))
+ (kmacro-tests-with-current-prefix-arg (kmacro-end-macro nil))))
+
+(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)))
+ (cond
+ ((consp where) (car where))
+ (t where))))
+
+
+(defun kmacro-tests-make-mock-read-func (events)
+ "Create around advice for `read-event' or `read-sequence'.
+If a keyboard macro is being executed, call the real function.
+Otherwise return the items in EVENTS one by one until
+they are exhausted, at which point start calling the real
+read function."
+ (lambda (orig-func &rest _args)
+ (if (or executing-kbd-macro (null events))
+ (funcall orig-func)
+ (prog1
+ (car events)
+ (setq events (cdr events))))))
+
+
+
+(provide 'kmacro-tests)
+
+;;; kmacro-tests.el ends here
--
2.10.1
next reply other threads:[~2016-11-13 21:23 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-11-13 21:23 Gemini Lasswell [this message]
2016-11-14 15:49 ` bug#24939: [PATCH] Add tests for lisp/kmacro.el Eli Zaretskii
2016-11-14 18:26 ` Gemini Lasswell
2016-11-14 18:47 ` Eli Zaretskii
2016-11-29 20:56 ` Gemini Lasswell
2016-11-30 9:08 ` Michael Albinus
2016-11-30 15:51 ` Eli Zaretskii
2016-11-30 16:51 ` Michael Albinus
2016-12-10 17:46 ` Gemini Lasswell
2016-12-31 17:42 ` Gemini Lasswell
2017-02-04 11:57 ` Eli Zaretskii
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=m2vavr5b14.fsf@rainbow.local \
--to=gazally@runbox.com \
--cc=24939@debbugs.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.