with-simulated-input-2.2/0000755000175000017500000000000013142162724015306 5ustar dogslegdogslegwith-simulated-input-2.2/README.md0000644000175000017500000000665713142162724016603 0ustar dogslegdogsleg# with-simulated-input [![Build Status](https://travis-ci.org/DarwinAwardWinner/with-simulated-input.svg?branch=master)](https://travis-ci.org/DarwinAwardWinner/with-simulated-input) [![MELPA Stable](https://stable.melpa.org/packages/with-simulated-input-badge.svg)](https://stable.melpa.org/#/with-simulated-input) This package provides an Emacs Lisp macro, `with-simulated-input`, which evaluates one or more forms while simulating a sequence of input events for those forms to read. The result is the same as if you had evaluated the forms and then manually typed in the same input. This macro is useful for non-interactive testing of normally interactive commands and functions, such as `completing-read`. For example: ```elisp (with-simulated-input "hello SPC world RET" (read-string "Say hello: ")) ``` This would return the string `"hello world"`. ## Simulating idleness Some interactive functions rely on idle timers to do their work, so you might need a way to simulate idleness. For that, there is the `wsi-simulate-idle-time` function. You can insert calls to this function in between input strings. For example, the following code will return `"hello world"`. ```elisp ;; Insert "world" after 500 seconds (run-with-idle-timer 500 nil 'insert "world") (with-simulated-input ;; Type "hello ", then "wait" 501 seconds, then type "RET" '("hello SPC" (wsi-simulate-idle-time 501) "RET") (read-string "Enter a string: ")) ``` Note that this code only *pretends* to be idle for 501 seconds. It actually runs immediately. In fact, you can put any lisp code in between input strings in this way. Get it from MELPA: https://stable.melpa.org/#/with-simulated-input ## Running the tests This package comes with a test suite. If you want to run it yourself, first install the [cask](http://cask.readthedocs.io/en/latest/) dependency manager. Then, from the package directory, run `cask install` to install all the development dependencies, in particular [buttercup](https://github.com/jorgenschaefer/emacs-buttercup). Finally, to run the tests, execute `cask exec buttercup -L .`. You should see something like this: ``` $ cask exec buttercup -L . Running 17 specs. `with-simulated-input' should work for basic string input should throw an error if the input is incomplete should allow the input to trigger errors should ignore extra input after BODY has completed should allow multiple functions in BODY to read input should allow aborting via C-g in KEYS used with `completing-read' should work with unambiguous tab completion should work with ambiguous tab completion should fail to exit with ambiguous completion and `require-match'Making completion list... should fail to exit with ambiguous completion and `require-match' using lisp forms in KEYS argument of `with-simulated-input' should allow evaluating arbitrary lisp forms should allow lisp forms to throw errors should not interpret lisp forms once BODY has finished `wsi-simulate-idle-time' should run idle timers should not run idle times with longer times should run idle timers added by other idle timers should run idle timers added by other idle timers when the new timer is in the past used within `with-simulated-input' should allow idle timers to trigger during simulated input Ran 17 specs, 0 failed, in 0.6 seconds. ```` Please run the tests before submitting any pull requests, and note in the pull request whether any of the tests fail. with-simulated-input-2.2/.travis.yml0000644000175000017500000000145013142162724017417 0ustar dogslegdogsleglanguage: generic env: matrix: - EMACS_VERSION=emacs-24.4 - EMACS_VERSION=emacs-24.5 - EMACS_VERSION=emacs-25.1 - EMACS_VERSION=emacs-25.2 - EMACS_VERSION=emacs-git-snapshot - EMACS_VERSION=remacs-git-snapshot matrix: allow_failures: - env: EMACS_VERSION=remacs-git-snapshot before_script: # Install evm - git clone https://github.com/rejeep/evm.git ~/.evm - export PATH="$HOME/.evm/bin:$PATH" - evm config path /tmp - evm list # use this version of emacs for tests - evm install "${EMACS_VERSION}-travis" --use --skip - evm list - emacs --version # Install cask - curl -fsSkL https://raw.github.com/cask/cask/master/go | python - export PATH="$HOME/.cask/bin:$PATH" # Install elisp dependencies - cask install script: cask exec buttercup -L . with-simulated-input-2.2/tests/0000755000175000017500000000000013142162724016450 5ustar dogslegdogslegwith-simulated-input-2.2/tests/test-with-simulated-input.el0000644000175000017500000001411413142162724024045 0ustar dogslegdogsleg;;; -*- lexical-binding: t -*- (require 'with-simulated-input) (require 'cl-lib) (require 'buttercup) ;; Needs to be dynamically bound (defvar mycollection) (describe "`with-simulated-input'" (it "should work for basic string input" (expect (with-simulated-input "hello RET" (read-string "Enter a string: ")) :to-equal "hello")) (it "should throw an error if the input is incomplete" (expect (lambda () (with-simulated-input "hello" (read-string "Enter a string: "))) :to-throw)) (it "should allow the input to trigger errors" (expect (lambda () (with-simulated-input "(error SPC \"Manually SPC throwing SPC an SPC error\") RET" (command-execute 'eval-expression))) :to-throw)) (it "should ignore extra input after BODY has completed" (expect (with-simulated-input "hello RET M-x eval-expression (error SPC \"Manually SPC throwing SPC an SPC error\") RET" (read-string "Enter a string: ")) :to-equal "hello")) (it "should allow multiple functions in BODY to read input" (expect (with-simulated-input "hello RET world RET" (list (read-string "First word: ") (read-string "Second word: "))) :to-equal '("hello" "world"))) (it "should allow aborting via C-g in KEYS" (expect (condition-case nil (with-simulated-input "C-g" (read-string "Enter a string: ")) (quit 'caught-quit)) :to-be 'caught-quit)) (describe "used with `completing-read'" :var (collection completing-read-function) (before-each (setq mycollection '("bluebird" "blueberry" "bluebell" "bluegrass" "baseball") completing-read-function #'completing-read-default)) ;; Unambiguous completion (it "should work with unambiguous tab completion" (expect ;; First TAB completes "blue", 2nd completes "bird" (with-simulated-input "bl TAB bi TAB RET" (completing-read "Choose: " mycollection)) :to-equal "bluebird")) (it "should work with ambiguous tab completion" (expect (with-simulated-input "bl TAB C-j" (completing-read "Choose: " mycollection)) :to-equal "blue")) (it "should fail to exit with ambiguous completion and `require-match'" ;; Suppress messages by replacing `message' with a stub (spy-on 'message) (expect (lambda () (with-simulated-input "bl TAB C-j" (completing-read "Choose: " mycollection nil t))) :to-throw))) (describe "using lisp forms in KEYS argument of `with-simulated-input'" (it "should allow evaluating arbitrary lisp forms" (expect (with-simulated-input '("hello SPC" (insert "world") "RET") (read-string "Enter a string: ")) :to-equal "hello world")) (it "should allow lisp forms to throw errors" (expect (lambda () (with-simulated-input '("hello SPC" (error "Throwing an error") "RET") (read-string "Enter a string: "))) :to-throw)) (it "should not interpret lisp forms once BODY has finished" (expect (with-simulated-input '("hello SPC world RET RET" (error "Should not reach this error")) (read-string "Enter a string: ")) :to-equal "hello world")) (it "should evaluate lisp forms in the proper lexical environment" (let ((my-lexical-var nil)) (with-simulated-input '("hello" (setq my-lexical-var t) "RET") (read-string "Enter a string: ")) (expect my-lexical-var :to-be-truthy))) (it "should allow interpolation of variables into KEYS" (let ((my-key-sequence "hello") (my-lisp-form '(insert " world"))) (expect (with-simulated-input (list my-key-sequence my-lisp-form "RET") (read-string "Enter a string: ")) :to-equal "hello world"))))) (defun idle-canary ()) (defvar timers-to-cancel nil) (defvar orig-timer--activate (symbol-function 'timer--activate)) (describe "`wsi-simulate-idle-time'" (spy-on 'idle-canary) (spy-on 'timer--activate :and-call-fake (lambda (timer &rest args) (push timer timers-to-cancel) (apply orig-timer--activate timer args))) (after-each (mapcar #'cancel-timer timers-to-cancel) (setq timers-to-cancel nil) (spy-calls-reset 'idle-canary)) (it "should run idle timers" (run-with-idle-timer 500 nil 'idle-canary) (wsi-simulate-idle-time 500) (expect 'idle-canary :to-have-been-called)) (it "should not run idle timers with longer times even when called multiple times" (run-with-idle-timer 500 nil 'set 'idle-canary) (wsi-simulate-idle-time 400) (wsi-simulate-idle-time 400) (wsi-simulate-idle-time 400) (expect 'idle-canary :not :to-have-been-called)) (it "should run idle timers added by other idle timers" (run-with-idle-timer 100 nil 'run-with-idle-timer 200 nil 'idle-canary) (wsi-simulate-idle-time 500) (expect 'idle-canary :to-have-been-called)) (it "should run idle timers added by other idle timers when the new timer is in the past" (run-with-idle-timer 100 nil 'run-with-idle-timer 50 nil 'idle-canary) (wsi-simulate-idle-time 500) (expect 'idle-canary :to-have-been-called)) (it "should run all idle timers when called with SECS = nil" (run-with-idle-timer 1000 nil 'idle-canary) (wsi-simulate-idle-time 1) (expect 'idle-canary :not :to-have-been-called) (wsi-simulate-idle-time) (expect 'idle-canary :to-have-been-called)) (describe "used within `with-simulated-input'" (it "should allow idle timers to trigger during simulated input" (run-with-idle-timer 500 nil 'insert "world") (expect (with-simulated-input '("hello SPC" (wsi-simulate-idle-time 501) "RET") (read-string "Enter a string: ")) :to-equal "hello world")))) ;;; test-with-simulated-input.el ends here with-simulated-input-2.2/with-simulated-input.el0000644000175000017500000002733213142162724021734 0ustar dogslegdogsleg;;; with-simulated-input.el --- A macro to simulate user input non-interactively -*- lexical-binding: t -*- ;; Copyright (C) 2017 Ryan C. Thompson ;; Filename: with-simulated-input.el ;; Author: Ryan C. Thompson ;; Created: Thu Jul 20 11:56:23 2017 (-0700) ;; Version: 2.2 ;; Package-Requires: ((emacs "24.4") (seq "2.0") (s "0")) ;; URL: ;; Keywords: lisp, tools, extensions ;; This file is NOT part of GNU Emacs. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Commentary: ;; This package provides a single macro, `with-simulated-input', which ;; evaluates one or more forms while simulating a sequence of input ;; events for those forms to read. The result is the same as if you ;; had evaluated the forms and then manually typed in the same input. ;; This macro is useful for non-interactive testing of normally ;; interactive commands and functions, such as `completing-read'. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This program 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. ;; ;; This program 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 . ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Code: (require 'cl-lib) (require 'seq) (require 's) (cl-defun wsi-key-bound-p (key) "Return non-nil if KEY is bound in any keymap. This function checks every keymap in `obarray' for a binding for KEY, and returns t if it finds and and nil otherwise. Note that this checks ALL keymaps, not just currently active ones." (catch 'bound (mapatoms (lambda (sym) (let ((keymap (when (boundp sym) (symbol-value sym)))) (when (keymapp keymap) (let ((binding (lookup-key keymap (kbd key)))) (when binding (throw 'bound t))))))) (throw 'bound nil))) (cl-defun wsi-get-unbound-key (&optional (modifiers '("C-M-A-s-H-" "C-M-A-s-" "C-M-A-H-")) (keys "abcdefghijklmnopqrstuvwxyz0123456789")) "Return a key binding that is not bound in any known keymap. This function will try check every letter from a to z and every number from 0 through 9 with several combinations of multiple modifiers (i.e. control, meta, alt, super, hyper). For each such key combination, it will check for bindings in all known keymaps, and return the first combination for which no such bindings exist. Thus, it should be safe to bind this key in a new keymap without interfering with any existing keymap. Optional arguments MODIFIERS and KEYS can be used the change the search space. MODIFIERS is a list of strings representing modifier combinations, e.g.: '(\"C-\" \"M-\" \"C-M-\") for control, meta, or both. KEYS is a string containing all keys to check. " (declare (advertised-calling-convention (&optional modifiers keys) nil)) (when (stringp modifiers) (setq modifiers (list modifiers))) (when (listp keys) (setq keys (apply #'concat keys))) (cl-loop named findkey for modifier in modifiers do (cl-loop for char across keys for bind = (concat modifier (string char)) when (not (wsi-key-bound-p bind)) do (cl-return-from findkey bind)) finally do (error "Could not find an unbound key with the specified modifiers"))) (defmacro wsi-current-lexical-environment () "Return the current lexical environment. This macro expands to a lisp form that evaluates to the current lexical environment. It works by creating a closure and then extracting and returning its lexical environment. This can be used to manually construct closures in that environment." `(let ((temp-closure (lambda () t))) (cl-assert (eq (car temp-closure) 'closure) t) (cadr temp-closure))) (defun wsi-make-closure (expr env) `(closure ,env () ,expr)) ;;;###autoload (defmacro with-simulated-input (keys &rest body) "Eval BODY forms with KEYS as simulated input. This macro is intended for automated testing of normally interactive functions by simulating input. If BODY tries to read user input (e.g. via `completing-read'), it will read input events from KEYS instead, as if the user had manually typed those keys after initiating evaluation of BODY. KEYS should be a string representing a sequence of key presses, in the format understood by `kbd'. In the most common case of typing in some text and pressing RET, KEYS would be something like `\"hello RET\"'. Note that spaced must be indicated explicitly using `SPC', e.g. `\"hello SPC world RET\"'. KEYS can also be a list. In this case, each element should either be a key sequence as described above or an arbitrary lisp form that will be evaluated at that point in the input sequence. For example, `\"hello RET\"' could also be written as: `((insert \"hello\") \"RET\")' If BODY tries to read more input events than KEYS provides, an error is signalled. This is to ensure that BODY will never block waiting for input, since this macro is intended for non-interactive use. If BODY does not consume all the input events in KEYS, the remaining input events in KEYS are discarded, and any remaining lisp forms in KEYS are never evaluated. Any errors generated by any means during the evaluation of BODY are propagated normally. The return value is the last form in BODY, as if it was wrapped in `progn'." (declare (indent 1)) `(cl-letf* ((lexenv (wsi-current-lexical-environment)) (next-action-key (wsi-get-unbound-key)) (canary-sym ',(cl-gensym "wsi-canary-")) (result canary-sym) (thrown-error nil) (body-form '(throw 'wsi-body-finished (progn ,@body))) (end-of-actions-form (list 'throw '(quote wsi-body-finished) (list 'quote canary-sym))) ;; Ensure KEYS is a list, and put the body form as the first ;; item and `C-g' as the last item (keylist ,keys) (keylist (if (listp keylist) keylist (list keylist))) ;; Replace non-strings with `next-action-key' and concat ;; everything together (full-key-sequence (cl-loop for action in keylist if (stringp action) collect action into key-sequence-list else collect next-action-key into key-sequence-list finally return ;; Prepend and append `next-action-key' to run body and canary (concat next-action-key " " (mapconcat #'identity key-sequence-list " ") " " next-action-key))) ;; Extract non-string forms, adding body at the front and ;; canary at the back (action-list (nconc (list body-form) (cl-loop for action in keylist if (not (stringp action)) collect action) (list end-of-actions-form))) ;; Wrap each action in a lexical closure so it can refer to ;; variables from the caller. (action-closures (cl-loop for action in action-list collect (wsi-make-closure action lexenv))) ;; Define the next action command with lexical scope so it can ;; access `action-closures'. ((symbol-function 'wsi-run-next-action) (lambda () (interactive) (condition-case err (if action-closures (let ((next-action (pop action-closures))) (funcall next-action)) (error "`with-simulated-input' reached end of action list without returning.")) (error (throw 'wsi-threw-error err))))) ;; Set up the temporary keymap (action-map (make-sparse-keymap))) ;; Finish setting up the keymap for the temp command (define-key action-map (kbd next-action-key) 'wsi-run-next-action) (setq thrown-error (catch 'wsi-threw-error (setq result (catch 'wsi-body-finished (let ((overriding-terminal-local-map action-map)) (execute-kbd-macro (kbd full-key-sequence))))) ;; If we got here, then no error (throw 'wsi-threw-error nil))) (when thrown-error (signal (car thrown-error) (cdr thrown-error))) (if (eq result canary-sym) (error "Reached end of simulated input while evaluating body") result))) (defvar wsi-simulated-idle-time nil) (defadvice current-idle-time (around simulate-idle-time activate) "Return the faked value while simulating idle time. While executing `wsi-simulate-idle-time', this advice causes the simulated idle time to be returned instead of the real value." (if wsi-simulated-idle-time (setq ad-return-value (when (time-less-p (seconds-to-time 0) wsi-simulated-idle-time) wsi-simulated-idle-time)) ad-do-it)) (cl-defun wsi-simulate-idle-time (&optional secs actually-wait) "Run all idle timers with delay less than SECS. This simulates resetting the idle time to zero and then being idle for SECS seconds. Hence calling this function twice with SECS = 1 is not equivalent to 2 seconds of idle time. If ACTUALLY-WAIT is non-nil, this function will also wait for the specified amount of time before running each timer. If SECS is nil, simulate enough idle time to run each timer in `timer-idle-list' at least once. (It's possible that some timers will be run more than once, since each timer could potentially add new timers to the list.) While each timer is running, `current-idle-time' will be overridden to return the current simulated idle time. This function does not run any timers in `timer-list', even though they would run during real idle time." (interactive "nSeconds of idle time: \nP") ;; SECS defaults to the maximum idle time of any currently active ;; timer. (unless secs (setq secs (cl-loop for timer in timer-idle-list maximize (float-time (timer--time timer))))) ;; Add a small fudge factor to deal with SECS being exactly equal to ;; a timer's time, to avoid floating point issues. (setq secs (+ secs 0.0001)) (cl-loop with already-run-timers = nil with stop-time = (seconds-to-time secs) with wsi-simulated-idle-time = (seconds-to-time 0) ;; We have to search `timer-idle-list' from the beginning each time ;; through the loop because each timer that runs might add more ;; timers to the list, and picking up at the same list position ;; would ignore those new timers. for next-timer = (car (cl-member-if-not (lambda (timer) (memq timer already-run-timers)) timer-idle-list)) while next-timer for previous-idle-time = wsi-simulated-idle-time if (time-less-p wsi-simulated-idle-time (timer--time next-timer)) do (setq wsi-simulated-idle-time (timer--time next-timer)) when actually-wait do (sleep-for (float-time (time-subtract wsi-simulated-idle-time previous-idle-time))) while (time-less-p wsi-simulated-idle-time stop-time) when (not (timer--triggered next-timer)) do (timer-event-handler next-timer) do (push next-timer already-run-timers) finally do (when actually-wait (sleep-for (float-time (time-subtract stop-time wsi-simulated-idle-time)))))) (provide 'with-simulated-input) ;;; with-simulated-input.el ends here with-simulated-input-2.2/setversion.sh0000755000175000017500000000075213142162724020052 0ustar dogslegdogsleg#!/bin/bash PKG_NAME="with-simulated-input" TARGET_VERSION="$1" if [ -n "$TARGET_VERSION" ]; then echo "Updating version to $TARGET_VERSION"; perl -i'orig_*' -lape "s/Version: [0-9.]+/Version: $TARGET_VERSION/g;" \ -e "s/((?:defconst|defvar|setq).*-version\s+)\"[0-9.]+\"/\${1}\"$TARGET_VERSION\"/g;" \ -e "s/\(package \"${PKG_NAME}\" \"[0-9.]+\"/(package \"${PKG_NAME}\" \"${TARGET_VERSION}\"/g" \ *.el Cask else echo "Usage: $0 VERSION_NUMBER" fi with-simulated-input-2.2/Cask0000644000175000017500000000063513142162724016116 0ustar dogslegdogsleg;; -*- mode: emacs-lisp -*- (source gnu) (source melpa) (package "with-simulated-input" "2.2" "A macro to simulate user input non-interactively.") (depends-on "seq") (depends-on "s") (development (depends-on "buttercup" :git "https://github.com/DarwinAwardWinner/emacs-buttercup.git" :branch "show-exprs" :files ("*.el" ("bin" "bin/*"))))