From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Gemini Lasswell Newsgroups: gmane.emacs.bugs Subject: bug#24939: [PATCH] Add tests for lisp/kmacro.el Date: Sun, 13 Nov 2016 13:23:51 -0800 Message-ID: NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1479072355 10830 195.159.176.226 (13 Nov 2016 21:25:55 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Sun, 13 Nov 2016 21:25:55 +0000 (UTC) To: 24939@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Sun Nov 13 22:25:49 2016 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c62H8-000792-Je for geb-bug-gnu-emacs@m.gmane.org; Sun, 13 Nov 2016 22:25:19 +0100 Original-Received: from localhost ([::1]:35097 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c62HB-0002Hb-OO for geb-bug-gnu-emacs@m.gmane.org; Sun, 13 Nov 2016 16:25:21 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:54593) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c62Gx-0002A2-3d for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 16:25:12 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c62Gr-0008MA-W2 for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 16:25:07 -0500 Original-Received: from debbugs.gnu.org ([208.118.235.43]:40734) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1c62Gr-0008M1-RP for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 16:25:01 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1c62Gr-0000dv-Li for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 16:25:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Gemini Lasswell Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 13 Nov 2016 21:25:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 24939 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.14790722742423 (code B ref -1); Sun, 13 Nov 2016 21:25:01 +0000 Original-Received: (at submit) by debbugs.gnu.org; 13 Nov 2016 21:24:34 +0000 Original-Received: from localhost ([127.0.0.1]:56133 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c62GO-0000d0-Rw for submit@debbugs.gnu.org; Sun, 13 Nov 2016 16:24:34 -0500 Original-Received: from eggs.gnu.org ([208.118.235.92]:35274) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1c62GM-0000ck-DY for submit@debbugs.gnu.org; Sun, 13 Nov 2016 16:24:32 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c62GC-00087L-2R for submit@debbugs.gnu.org; Sun, 13 Nov 2016 16:24:25 -0500 Original-Received: from lists.gnu.org ([2001:4830:134:3::11]:50641) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1c62GB-00087H-TY for submit@debbugs.gnu.org; Sun, 13 Nov 2016 16:24:19 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:54440) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c62G6-00024h-DI for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 16:24:19 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c62G0-000856-Hy for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 16:24:14 -0500 Original-Received: from aibo.runbox.com ([91.220.196.211]:59477) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1c62Fz-000847-QK for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 16:24:08 -0500 Original-Received: from [10.9.9.212] (helo=mailfront12.runbox.com) by bars.runbox.com with esmtp (Exim 4.71) (envelope-from ) id 1c62Fx-0007VW-FG for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 22:24:05 +0100 Original-Received: from c-24-22-244-161.hsd1.wa.comcast.net ([24.22.244.161] helo=rainbow.local) by mailfront12.runbox.com with esmtpsa (uid:179284 ) (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) id 1c62Fu-0007DR-Ib for bug-gnu-emacs@gnu.org; Sun, 13 Nov 2016 22:24:04 +0100 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 208.118.235.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.org gmane.emacs.bugs:125673 Archived-At: --=-=-= Content-Type: text/plain 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. --=-=-= Content-Type: text/plain; charset=utf-8 Content-Disposition: attachment; filename=0001-Add-tests-for-lisp-kmacro.el.patch Content-Transfer-Encoding: quoted-printable >From 6dc518ac6f356a55593184ed6e1bafa5cd764623 Mon Sep 17 00:00:00 2001 From: gazally 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 . + +;;; 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 :st= ub + ((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=3D%s") + (kmacro-tests-with-current-prefix-arg (kmacro-set-counter 50)) + (kmacro-tests-should-insert "c=3D50" + (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 emp= ty + ;; 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 macr= o. +\(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 "=3D%d=3D") + (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-repe= at)) + (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 event= s)) + (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 "=E1=B8=A9i there" + :macro-result "\C-cxq17051i there"))) + +(kmacro-tests-deftest kmacro-tests-step-edit-step-through-negative-argumen= t () + "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-inse= rt () + "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-=E1=B8=A9i" + :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 event= s)) + (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 + (>=3D (length arglists) (length event-keys))) + :around + (lambda (&rest args) + (when states + (should (equal (car current-state) (buffer-stri= ng))) + (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) arg= s) + (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 --=20 2.10.1 --=-=-=--