mirror of
https://git.savannah.gnu.org/git/emacs/org-mode.git
synced 2024-09-29 23:17:49 +00:00
Initial ERT test: org-test.el
This commit is contained in:
parent
e0c70d0a1a
commit
399e488146
94
testing/README.org
Normal file
94
testing/README.org
Normal file
|
@ -0,0 +1,94 @@
|
|||
#+OPTIONS: ^:nil
|
||||
|
||||
|
||||
* Testing elisp files with ERT
|
||||
|
||||
The aim is to provide a few simple yet helpfull commands for testing
|
||||
while hacking on elisp files. The entire thing is based on
|
||||
conventions. You don't need to care for those conventions, but it
|
||||
makes live easier.
|
||||
|
||||
Currently three commands are provided:
|
||||
|
||||
* =org-test-edit-buffer-file-tests= :: Open the file with tests for
|
||||
the current buffer. If the file and it's parent directories do
|
||||
not yet exist, create them. If the file did not yet exist,
|
||||
insert a little template test to get started.
|
||||
|
||||
* =org-test-test-current-defun= :: Look up test files for defun at
|
||||
point and execute the tests. This is done by searching an
|
||||
elisp file with the name of the current defun plus ".el". In
|
||||
case your point is in defun DEFUN in the file
|
||||
=proj/lisp/FILE.el=, this command will search the directory tree
|
||||
up until it finds a directory named =testing/=. If there is a
|
||||
directory =proj/testing/= it assumes your tests are in
|
||||
=proj/testing/lisp/FILE.el/DEFUN.el=. If that file is found, it
|
||||
will be loaded and all ERT tests for the selector "^DEFUN" will
|
||||
be executed. If that file does not exist, fall back on loading
|
||||
=proj/testing/lisp/FILE.el/tests.el=.
|
||||
|
||||
* =org-test-test-buffer-file= :: Look up a test file for
|
||||
`buffer-file-name' and execute the tests. This is done by
|
||||
searching for a directory =testing/= from `buffer-file-name's
|
||||
directory upwards and then apply the relative path to your
|
||||
source file there. In case you're editing =proj/lisp/FILE.el=,
|
||||
and a directory =proj/testing/= exists, this command will try to
|
||||
load =proj/testing/lisp/FILE.el/tests.el= and other elisp files
|
||||
in that directory. The resulting list of files will be loaded
|
||||
and all ERT tests for selector "^FILE" will be executed. With
|
||||
a prefix argument, load only =tests.el=.
|
||||
|
||||
* =org-test-run-all-tests= :: Run all tests for all lisp files and all
|
||||
defuns in your project.
|
||||
|
||||
|
||||
The first two commands call `ert-delete-all-tests' to make sure that
|
||||
only the desired tests are executed. All functions load the test
|
||||
files for each call, hence always a current version of tests are
|
||||
used. (This might change in the future...)
|
||||
|
||||
|
||||
* Getting started
|
||||
|
||||
You should obtain ERT by cloning Christian Ohler's public git
|
||||
repository:
|
||||
|
||||
: sh$ git clone http://github.com/ohler/ert.git
|
||||
|
||||
Ensure that the =ert/= directory is in your loadpath:
|
||||
|
||||
: (add-to-list 'load-path "~/path/to/ert/")
|
||||
|
||||
|
||||
*** Where to put the tests
|
||||
|
||||
The tests live in a directory (e.g. =proj/testing/=) that closely
|
||||
resembles the directory structure below =proj/=.
|
||||
|
||||
For example let's assume a file =proj/lisp/FILE.el= exists. To write tests
|
||||
for this file, create a directory structure in =proj/testing/= with the relative
|
||||
path to your file plus the name of that file as directory:
|
||||
=proj/testing/lisp/FILE.el/=
|
||||
|
||||
Org-test searches that directory for a file named =tests.el= and
|
||||
executes all ERT tests that match the selector "=^FILE=".
|
||||
|
||||
To run a test, you might want to use one of the two commands
|
||||
provided by =org-test.el= (see above).
|
||||
|
||||
|
||||
* TODOs
|
||||
|
||||
*** TODO Setup a buffers for testing
|
||||
|
||||
***** TODO Compare the contents of such a buffer
|
||||
...with a control file.
|
||||
|
||||
*** TODO Provide directory and file functions
|
||||
Provide little services to help test writers with temporary
|
||||
buffers, files and directories.
|
||||
|
||||
Or just use temp-files for that?
|
||||
|
||||
*** TODO let-bind different setups for tests
|
||||
Maybe just provide an example on how to do that.
|
295
testing/org-test.el
Normal file
295
testing/org-test.el
Normal file
|
@ -0,0 +1,295 @@
|
|||
;;;; org-test.el --- Tests for Org-mode
|
||||
|
||||
;; Copyright (c) 2010 Sebastian Rose, Hannover, Germany
|
||||
;; Authors:
|
||||
;; Sebastian Rose, Hannover, Germany, sebastian_rose gmx de
|
||||
|
||||
;; Released under the GNU General Public License version 3
|
||||
;; see: http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
;;;; Comments:
|
||||
|
||||
;; Interactive testing for Org mode.
|
||||
|
||||
;; The heart of all this is the commands
|
||||
;; `org-test-test-current-defun'. If called while in an emacs-lisp
|
||||
;; file, org-test first searches for a directory testing/tests/NAME/,
|
||||
;; where name is the basename of the lisp file you're in. This
|
||||
;; directory is then searched for a file named like the defun the
|
||||
;; point is in. If that failes, a file named 'tests.el' is searched
|
||||
;; in this directory. The file found is loaded and
|
||||
;; `org-test-run-tests' is called with the prefix "^NAME-OF-DEFUN".
|
||||
|
||||
;; The second usefull function is `org-test-test-buffer-file'. This
|
||||
;; function searches the same way as `org-test-test-current-defun'
|
||||
;; does, but only for the tests.el file. All tests in that file with
|
||||
;; the prefix "^BUFFER-FILE-NAME" with the ".el" suffix stripped are
|
||||
;; executed.
|
||||
|
||||
;;; Prerequisites:
|
||||
|
||||
;; You'll need to download and install ERT to use this stuff. You can
|
||||
;; get ERT like this:
|
||||
;; sh$ git clone http://github.com/ohler/ert.git
|
||||
|
||||
|
||||
|
||||
;;;; Code:
|
||||
|
||||
(require 'ert-batch)
|
||||
(require 'ert)
|
||||
(require 'ert-exp)
|
||||
(require 'ert-exp-t)
|
||||
(require 'ert-run)
|
||||
(require 'ert-ui)
|
||||
|
||||
(require 'org)
|
||||
|
||||
|
||||
|
||||
(defconst org-test-default-test-file-name "tests.el"
|
||||
"For each defun a separate file with tests may be defined.
|
||||
tests.el is the fallback or default if you like.")
|
||||
|
||||
(defconst org-test-default-directory-name "testing"
|
||||
"Basename or the directory where the tests live.
|
||||
org-test searches this directory up the directory tree.")
|
||||
|
||||
|
||||
|
||||
;;; Find tests
|
||||
|
||||
(defun org-test-test-directory-for-file (file)
|
||||
"Search up the directory tree for a directory
|
||||
called like `org-test-default-directory-name'.
|
||||
If that directory is not found, ask the user.
|
||||
|
||||
Return the name of the directory that should contain tests for
|
||||
FILE regardless of it's existence.
|
||||
|
||||
If the directory `org-test-default-directory-name' cannot be
|
||||
found up the directory tree, return nil."
|
||||
(let* ((file (file-truename
|
||||
(or file buffer-file-name)))
|
||||
(orig
|
||||
(file-name-directory
|
||||
(expand-file-name (or file buffer-file-name))))
|
||||
(parent orig)
|
||||
(child "")
|
||||
base)
|
||||
(catch 'dir
|
||||
(progn
|
||||
(while (not (string= parent child))
|
||||
(let ((td (file-name-as-directory
|
||||
(concat parent
|
||||
org-test-default-directory-name))))
|
||||
(when (file-directory-p td)
|
||||
(setq base parent)
|
||||
(throw 'dir parent))
|
||||
(setq child parent)
|
||||
(setq parent (file-name-as-directory
|
||||
(file-truename (concat parent ".."))))))
|
||||
(throw 'dir nil)))
|
||||
|
||||
(if base
|
||||
;; For now, rely on the fact, that if base exists, the rest of
|
||||
;; the directory setup is as expected, too.
|
||||
(progn
|
||||
(file-name-as-directory
|
||||
(concat
|
||||
(file-name-as-directory
|
||||
(file-truename
|
||||
(concat
|
||||
(file-name-as-directory
|
||||
(concat base org-test-default-directory-name))
|
||||
(file-relative-name orig base))))
|
||||
(file-name-nondirectory file))))
|
||||
;; TODO:
|
||||
;; it's up to the user to find the directory for the file he's
|
||||
;; testing...
|
||||
;; (setq base (read-directory-name
|
||||
;; "Testdirectory: " orig orig t))
|
||||
nil)))
|
||||
|
||||
(defun org-test-test-file-name-for-file (directory file)
|
||||
"Return the name of the file that should contain the tests for FILE.
|
||||
FILE might be a path or a base filename.
|
||||
Return nil if no file tests for FILE exists."
|
||||
;; TODO: fall back on a list of all *.el files in this directory.
|
||||
(let ((tf (concat directory
|
||||
org-test-default-test-file-name)))
|
||||
(if (file-exists-p tf)
|
||||
tf
|
||||
nil)))
|
||||
|
||||
(defun org-test-test-file-name-for-defun (directory fun &optional file)
|
||||
"Return the name of the file that might or might not contain tests
|
||||
for defun FUN (a string) defined FILE. Return nil if no file with
|
||||
special tests for FUN exists."
|
||||
(let* ((funsym (intern fun))
|
||||
(file (or file
|
||||
(find-lisp-object-file-name
|
||||
(intern fun)
|
||||
(symbol-function (intern fun)))))
|
||||
(tf (concat directory fun ".el")))
|
||||
(if (file-exists-p tf)
|
||||
tf
|
||||
nil)))
|
||||
|
||||
|
||||
|
||||
;;; TODO: Test buffers and control files
|
||||
|
||||
(defun org-test-buffer (&optional file)
|
||||
"TODO: Setup and return a buffer to work with.
|
||||
If file is non-nil insert it's contents in there.")
|
||||
|
||||
(defun org-test-compare-with-file (&optional file)
|
||||
"TODO: Compare the contents of the test buffer with FILE.
|
||||
If file is not given, search for a file named after the test
|
||||
currently executed.")
|
||||
|
||||
|
||||
|
||||
;;; Run tests
|
||||
|
||||
(defun org-test-run-tests (&optional selector)
|
||||
"Run all tests matched by SELECTOR.
|
||||
SELECTOR defaults to \"^org\".
|
||||
See the docstring of `ert-select-tests' for valid selectors.
|
||||
Tests are run inside
|
||||
(let ((deactivate-mark nil))
|
||||
(save-excursion
|
||||
(save-match-data
|
||||
...)))."
|
||||
(interactive)
|
||||
(let ((select (or selector "^org"))
|
||||
(deactivate-mark nil))
|
||||
(save-excursion
|
||||
(save-match-data
|
||||
(ert select)))))
|
||||
|
||||
(defun org-test-run-all-tests ()
|
||||
"Run all defined tests matching \"^org\".
|
||||
Unlike `org-test-run-tests', load all test files first.
|
||||
Uses `org-test-run-tests' to run the actual tests."
|
||||
(interactive)
|
||||
(let* ((org-dir
|
||||
(file-name-directory
|
||||
(find-lisp-object-file-name 'org-mode 'function)))
|
||||
(org-files
|
||||
(directory-files org-dir nil "\\.el")))
|
||||
(message "Loading all tests....")
|
||||
(mapc
|
||||
(lambda (f)
|
||||
(let* ((dir (org-test-test-directory-for-file f)))
|
||||
(when (and dir (file-directory-p dir))
|
||||
(let ((tfs (directory-files dir t "\\.el")))
|
||||
(mapc (lambda (tf)
|
||||
(load-file tf))
|
||||
tfs)))))
|
||||
org-files)
|
||||
(org-test-run-tests)))
|
||||
|
||||
|
||||
|
||||
;;; Commands:
|
||||
|
||||
(defun org-test-test-current-defun ()
|
||||
"Execute all tests for function at point if tests exist."
|
||||
(interactive)
|
||||
(ert-delete-all-tests)
|
||||
(save-excursion
|
||||
(save-match-data
|
||||
(end-of-line)
|
||||
(beginning-of-defun)
|
||||
(when (looking-at "(defun[[:space:]]+\\([^([:space:]]*\\)[[:space:]]*(")
|
||||
(let* ((fun (match-string-no-properties 1))
|
||||
(dir (org-test-test-directory-for-file buffer-file-name))
|
||||
(tf (or (org-test-test-file-name-for-defun
|
||||
dir fun buffer-file-name)
|
||||
(org-test-test-file-name-for-file dir buffer-file-name))))
|
||||
(if tf
|
||||
(progn
|
||||
(load-file tf)
|
||||
(org-test-run-tests
|
||||
(concat "^" fun)))
|
||||
(error "No test files found for \"%s\"" fun)))))))
|
||||
|
||||
(defun org-test-test-buffer-file (&optional only)
|
||||
"Run all tests for current `buffer-file-name' if tests exist.
|
||||
If ONLY is non-nil, use the `org-test-default-test-file-name'
|
||||
file only."
|
||||
(interactive "P")
|
||||
(ert-delete-all-tests)
|
||||
(let* ((pref
|
||||
(concat
|
||||
"^"
|
||||
(file-name-sans-extension
|
||||
(file-name-nondirectory buffer-file-name))))
|
||||
(dir (org-test-test-directory-for-file buffer-file-name))
|
||||
(tfs (if only
|
||||
(list
|
||||
(org-test-test-file-name-for-file
|
||||
dir buffer-file-name))
|
||||
(directory-files dir t "\\.el$"))))
|
||||
(if (car tfs)
|
||||
(mapc
|
||||
(lambda (tf)
|
||||
(load-file tf)
|
||||
(org-test-run-tests pref))
|
||||
tfs)
|
||||
(error "No %s found for \"%s\""
|
||||
(if only
|
||||
(format "file \"%s\"" org-test-default-test-file-name)
|
||||
"test files")
|
||||
buffer-file-name))))
|
||||
|
||||
(defun org-test-edit-buffer-file-tests ()
|
||||
"Open the `org-test-default-test-file-name' file for editing.
|
||||
If the file (and parent directories) do not yet exist,
|
||||
create them."
|
||||
(interactive)
|
||||
(save-match-data
|
||||
;; Check, if editing an emacs-lisp file
|
||||
(unless
|
||||
(string-match "\\.el$" buffer-file-name)
|
||||
(error "Not an emacs lisp file: %s" buffer-file-name)))
|
||||
|
||||
(let ((dir (org-test-test-directory-for-file
|
||||
buffer-file-name)))
|
||||
(unless dir
|
||||
(error "Directory %s not found. Sorry."
|
||||
org-test-default-directory-name))
|
||||
|
||||
(let* ((tf (concat dir org-test-default-test-file-name))
|
||||
(exists (file-exists-p tf))
|
||||
(rel (file-relative-name buffer-file-name dir))
|
||||
(tprefix (file-name-nondirectory
|
||||
(file-name-sans-extension buffer-file-name))))
|
||||
(unless (file-directory-p dir) ; FIXME: Ask?
|
||||
(make-directory dir t))
|
||||
(find-file tf)
|
||||
(unless exists
|
||||
(insert
|
||||
";;; " org-test-default-test-file-name " --- Tests for "
|
||||
(replace-regexp-in-string "^\\(?:\\.+/\\)+" "" rel)
|
||||
"\n\n"
|
||||
"\n"
|
||||
";;; Code:\n"
|
||||
"(require 'org-test)\n"
|
||||
"(unless (fboundp 'org-test-run-all-tests)\n"
|
||||
" (error \"%s\" \"org-test.el not loaded. Giving up.\"))\n"
|
||||
"\n"
|
||||
"\n"
|
||||
";;; Tests\n"
|
||||
"(ert-deftest " tprefix "/example-test ()\n"
|
||||
" \"Just an example to get you started.\"\n"
|
||||
" (should t)\n"
|
||||
" (should-not nil)\n"
|
||||
" (should-error (error \"errr...\")))\n")))))
|
||||
|
||||
|
||||
|
||||
(provide 'org-test)
|
||||
;;; org-test.el ends here
|
Loading…
Reference in a new issue