Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test an interactive function in emacs?

I'm the maintainer of an Emacs package whose entire purpose is related to user interaction. Whenever I make a change to the code, I have to test it out manually to see if it works. It would be nice if I could do some automated testing, but I have no idea how to simulate user input in the way that would be required to do so. Is there any way to run an interactive function that will prompt the user and then respond to that prompt with simulated typing (including key combinations like C-j)?

like image 815
Ryan C. Thompson Avatar asked Oct 06 '15 04:10

Ryan C. Thompson


People also ask

How do I execute a function in Emacs?

To run the function, use M-: ( eval-expression ) and type (count-words-buffer) then RET . If the function needed arguments, you'd need to add them after the function name, e.g. (my-function "first argument" 'second-argument) . Alternatively, go to the *scratch* buffer and type your code (e.g. (count-word-buffers) ).

What is a function interactive?

A Lisp function becomes a command when its body contains, at top level, a form that calls the special form ` (interactive...) '. This special form does nothing when executed, but its presence in the function definition indicates that interactive calling is permitted.

What is Emacs Lisp used for?

Emacs Lisp is a dialect of the Lisp programming language used as a scripting language by Emacs (a text editor family most commonly associated with GNU Emacs and XEmacs). It is used for implementing most of the editing functionality built into Emacs, the remainder being written in C, as is the Lisp interpreter.

What is ERT in software testing?

ERT is a tool for automated testing in Emacs Lisp. Its main features are facilities for defining tests, running them and reporting the results, and for debugging test failures interactively.


2 Answers

New: I have reimplemented my with-simulated-input macro using execute-kbd-macro, so it now works in batch mode as well. You can view the new implementation here.

Original answer:

I found the answer in a question on the Emacs SX site. Basically, you have to put the desired sequence of keys into unread-command-events after converting it to the proper format. For example:

(let ((unread-command-events (listify-key-sequence (kbd "blu RET"))))
  (ido-completing-read "Select a color: " '("yellow" "blue")))

Properly returns "blue".

One thing to be careful about is that you have to make sure that your key sequence will definitely terminate the interactive part of the command, or else the command will continue waiting for input. One way to ensure termination is to append "C-g" to the key sequence, which will abort the command if it hasn't finished by the time it gets to the end of the key sequence. If the command does finish, then any unused input will be discarded when the let-binding goes out of scope, so the C-g event will not signal an error. So a more proper test might be:

;; Runs successfully
(condition-case nil
    (let ((unread-command-events (listify-key-sequence (kbd "blu RET C-g"))))
      (ido-completing-read "Select a color: " '("yellow" "blue")))
  (quit (error "Reached end of `unread-command-events' without terminating")))

;; Throws an error
(condition-case nil
    (let ((unread-command-events (listify-key-sequence (kbd "blu C-g"))))
      (ido-completing-read "Select a color: " '("yellow" "blue")))
  (quit (error "Reached end of `unread-command-events' without terminating")))

An important caveat to this approach is that despite allowing you to run an interactive function completely non-interactively, it will not work in a batch-mode emacs session, presumably because emacs doesn't process keyboard input at all in batch mode.

like image 132
Ryan C. Thompson Avatar answered Oct 24 '22 16:10

Ryan C. Thompson


You can make use of execute-kbd-macro and ERT. I have a very simple implementation in my test file. The tests actually look very neat - almost as if I was pressing the keys interactively:

(ert-deftest ivy-read ()
  (should (equal
           (ivy-with '(ivy-read "pattern: " '("blue" "yellow"))
                     "C-m")
           "blue"))
  (should (equal
           (ivy-with '(ivy-read "pattern: " '("blue" "yellow"))
                     "y C-m")
           "yellow"))
  (should (equal
           (ivy-with '(ivy-read "pattern: " '("blue" "yellow"))
                     "y DEL b C-m")
           "blue"))
  (should (equal
           (ivy-with '(ivy-read "pattern: " '("blue" "yellow"))
                     "z C-m")
           "z")))
like image 44
abo-abo Avatar answered Oct 24 '22 15:10

abo-abo