Initial ERT test: org-test.el

This commit is contained in:
Sebastian Rose, Hannover, Germany 2010-10-02 23:23:27 +02:00 committed by Eric Schulte
parent e0c70d0a1a
commit 399e488146
2 changed files with 389 additions and 0 deletions

94
testing/README.org Normal file
View 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
View 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