From 399e48814632a46e7aa55a39ae679e4da60854f9 Mon Sep 17 00:00:00 2001 From: "Sebastian Rose, Hannover, Germany" Date: Sat, 2 Oct 2010 23:23:27 +0200 Subject: [PATCH] Initial ERT test: org-test.el --- testing/README.org | 94 ++++++++++++++ testing/org-test.el | 295 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 389 insertions(+) create mode 100644 testing/README.org create mode 100644 testing/org-test.el diff --git a/testing/README.org b/testing/README.org new file mode 100644 index 000000000..92f2ab4af --- /dev/null +++ b/testing/README.org @@ -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. diff --git a/testing/org-test.el b/testing/org-test.el new file mode 100644 index 000000000..a9c8f2741 --- /dev/null +++ b/testing/org-test.el @@ -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