From cef681d278ed3703a0f0a8e29b516f44c6456859 Mon Sep 17 00:00:00 2001 From: Phil Jackson Date: Sun, 3 Aug 2008 17:46:29 +0100 Subject: [PATCH] Initial checkin --- .gitignore | 2 + Makefile | 54 +++++++ etest-result-mode.el | 204 ++++++++++++++++++++++++++ etest-style.css | 40 ++++++ etest.el | 243 +++++++++++++++++++++++++++++++ etest.etest | 46 ++++++ etest.texinfo | 395 +++++++++++++++++++++++++++++++++++++++++++++++++++ todo.org | 14 ++ 8 files changed, 998 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 etest-result-mode.el create mode 100644 etest-style.css create mode 100644 etest.el create mode 100644 etest.etest create mode 100644 etest.texinfo create mode 100644 todo.org diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95bb424 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.html +*.info diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c92a958 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +EMACS=emacs +GZIP=gzip +ALLSOURCE=$(wildcard *.el) +ALLCOMPILED=$(wildcard *.elc) +SPECIAL= +SOURCE=$(filter-out $(SPECIAL),$(ALLSOURCE)) +TARGET=$(patsubst %.el,%.elc,$(SOURCE)) + +DESTDIR= +PREFIX=$(DESTDIR)/usr/local +INFODIR=$(PREFIX)/info +MAN1DIR=$(PREFIX)/share/man/man1 +SITELISP=$(PREFIX)/share/emacs/site-lisp/etest + +INSTALLINFO = /usr/sbin/install-info --info-dir=$(INFODIR) + +.PHONY: all install deb-install clean +.PRECIOUS: %.elc %.info %.html +all: $(TARGET) etest.info + +%.elc: %.el + @$(EMACS) --eval "(add-to-list 'load-path \".\")" \ + -q --no-site-file \ + -batch \ + -f batch-byte-compile $< + +%.info: %.texinfo + makeinfo --no-split $< + +%.html: %.texinfo etest-style.css + makeinfo --css-include etest-style.css --html --no-split $< + +install: + test -d $(SITELISP) || mkdir -p $(SITELISP) + [ -d $(INFODIR) ] || install -d $(INFODIR) + install -m 644 $(ALLSOURCE) $(SITELISP) + install -m 644 $(ALLCOMPILED) $(SITELISP) + install -m 0644 etest.info $(INFODIR)/etest + for p in $(MAN1PAGES) ; do $(GZIP) -9c $$p > $(MAN1DIR)/$$p.gz ; done + $(INSTALLINFO) etest.info + +html: etest.html + +remove-info: + $(INSTALLINFO) --remove etest.info + +deb-install: + install -m 644 $(ALLSOURCE) $(SITELISP) + +ChangeLog: + git log > $@ + +clean: + -rm -f *~ *.elc etest.info etest.html diff --git a/etest-result-mode.el b/etest-result-mode.el new file mode 100644 index 0000000..861cf8d --- /dev/null +++ b/etest-result-mode.el @@ -0,0 +1,204 @@ +(require 'outline) + +(declare-function etest-resultp "etest") + +;; calm down byte-compiler, you can have this one... +(eval-when-compile + (defvar current-results) + (require 'cl)) + +;; The grouping of the status is for convenience and use by the +;; font-locking so if you change the re don't forget to capture the +;; status +(defvar etest-rm-not-ok-re "^ *\\(not ok\\) \\.\\." + "Regexp that will match bad test status.") + +(defvar etest-rm-ok-re "^ *\\(ok\\) \\.\\." + "Regexp that will match good test status.") + +(defvar etest-status-re + (concat "\\(" etest-rm-not-ok-re + "\\|" etest-rm-ok-re + "\\)") + "Regexp that will match a test status.") + +(defvar etest-rm-map + (let ((m (make-keymap))) + (define-key m (kbd "q") 'bury-buffer) + (define-key m (kbd "g") + '(lambda () + (interactive) + (etest-rm-refresh-buffer current-results))) + (define-key m (kbd "#") 'etest-rm-cycle-comments) + (define-key m (kbd "") 'etest-rm-toggle-headline) + m)) + +(defvar etest-rm-comment-visibility-types + '(show-all show-not-ok show-ok show-none)) + +(defvar etest-rm-comment-visibility-map + '((show-all etest-rm-show-all-comments) + (show-not-ok etest-rm-hide-ok-comments) + (show-ok etest-rm-hide-not-ok-comments) + (show-none etest-rm-hide-all-comments)) + "Defines how the result buffer should look when the user is +toggling visibility states.") + +(defun etest-rm-count-string-at-bol (string) + "Count how many instances of STRING are at the start of the +current line." + (save-excursion + (goto-char (point-at-bol)) + (narrow-to-region (point) (point-at-eol)) + (let ((count 0)) + (while (looking-at string) + (forward-char) + (setq count (1+ count))) + (widen) + count))) + +(defun etest-rm-outline-level () + "Calculate what the current outline level should be. See +`ouline-level' for explination." + ;; we add one becuase there is an extra space before a status + (1+ (if (looking-at etest-status-re) + (etest-rm-count-string-at-bol " ") + (etest-rm-count-string-at-bol "*")))) + +;;;###autoload +(defun etest-result-mode (&optional results) + "Mode used to display test results." + (interactive) + (kill-all-local-variables) + (setq buffer-read-only t) + (outline-minor-mode) + (set (make-local-variable 'outline-regexp) + (concat "\\(\\*\\|" etest-status-re "\\)")) + (set (make-local-variable 'outline-level) + 'etest-rm-outline-level) + (set (make-local-variable 'current-results) results) + (setq major-mode 'etest-result-mode) + (setq mode-name "etest-result") + (set (make-local-variable 'font-lock-defaults) + '(etest-rm-font-lock-keywords t t)) + (use-local-map etest-rm-map) + (funcall (cadr (assoc (car etest-rm-comment-visibility-types) + etest-rm-comment-visibility-map)))) + +(defun etest-rm-pretty-print-status (result level) + "The pretty printing of a single test result. " + (let ((returned (plist-get result :result))) + (let* ((doc (plist-get result :doc)) + (comments (plist-get result :comments)) + (prefix (if returned "ok" "not ok"))) + (insert (concat " " prefix " ")) + (insert-char ?\. (- 18 (length prefix) level)) + (insert ".. ") + (let ((col (current-column))) + (insert (concat doc "\n")) + (when comments + (mapc + (lambda (comment) + (indent-to col) + (insert (concat "# " comment "\n"))) + (split-string comments "\n" t))))))) + +(defun etest-rm-pretty-print-results (results &optional level) + "Pretty print the results of a run to a buffer. See also +`etest-rm-pretty-print-status'." + (let ((level (or level 0)) + (res (car results))) + (cond + ((stringp res) + (insert-char ?\* (1+ level)) + (insert (concat " " res "\n")) + (setq level (1+ level))) + ((etest-resultp res) + (indent-to level) + (etest-rm-pretty-print-status res level)) + ((listp res) + (etest-rm-pretty-print-results res level))) + (when (cdr results) + (etest-rm-pretty-print-results (cdr results) level)))) + +(defun etest-rm-refresh-buffer (results) + "Refresh the results buffer using the cached test results." + (save-selected-window + (switch-to-buffer-other-window (get-buffer-create "*etest*")) + (setq buffer-read-only nil) + (erase-buffer) + (etest-rm-pretty-print-results results 0) + (etest-result-mode results) + (goto-char (point-min)) + (when (search-forward-regexp etest-rm-not-ok-re nil t) + (goto-char (point-at-bol))))) + +(defconst etest-rm-font-lock-keywords + `((,etest-rm-ok-re 1 font-lock-keyword-face) + (,etest-rm-not-ok-re 1 font-lock-warning-face) + ("^ *\\(#.+\\)" 1 font-lock-comment-face) + ("^ *\\*+ \\(.+\\)" 1 font-lock-variable-name-face))) + +(defun etest-rm-toggle-headline () + "Toggle the visibility of a test category." + (interactive) + (unless (looking-at outline-regexp) + (outline-previous-heading)) + (if (get-char-property (point-at-eol) 'invisible) + (show-subtree) + (hide-subtree))) + +;;; comment toggling etc... + +(defun etest-rm-cycle-comments () + "Shift the values in `etest-rm-comment-visibility-types' and +use the `car' of that list to determine the visibility of +comments." + (interactive) + (setq etest-rm-comment-visibility-types + (concatenate 'list + (cdr etest-rm-comment-visibility-types) + (list (car etest-rm-comment-visibility-types)))) + (etest-rm-refresh-buffer current-results) + (message (format "%S" (car etest-rm-comment-visibility-types)))) + +(defmacro etest-with-comments (&rest body) + "Eval BODY on each comment in the results buffer." + `(save-excursion + (goto-char (point-min)) + (while (search-forward-regexp etest-status-re nil t) + (outline-previous-heading) + ,@body + (forward-line)))) + +(defun etest-rm-hide-not-ok-comments () + "Hide all comments associated with a passing test in a result +buffer." + (interactive) + (etest-with-comments + (if (looking-at etest-rm-not-ok-re) + (hide-subtree) + (show-subtree)))) + +(defun etest-rm-hide-ok-comments () + "Hide all comments associated with a passing test in a result +buffer." + (interactive) + (etest-with-comments + (if (looking-at etest-rm-ok-re) + (hide-subtree) + (show-subtree)))) + +(defun etest-rm-hide-all-comments () + "Hide all comments in a result buffer." + (interactive) + (etest-with-comments + (hide-subtree))) + +(defun etest-rm-show-all-comments () + "Show all comments in a result buffer." + (interactive) + (etest-with-comments + (show-subtree))) + +(provide 'etest-result-mode) diff --git a/etest-style.css b/etest-style.css new file mode 100644 index 0000000..1c3b9fc --- /dev/null +++ b/etest-style.css @@ -0,0 +1,40 @@ +html { + background-color: #222; + padding: 20px; +} + +.menu,.node { display: none } + +body { + font: 100% arial, helvetica, sans-serif; + min-width: 650px; + width: 700px; + display: block; + margin: 0 auto; + padding: 20px; + background-color: #fff; + border: 1px solid #111; +} + +h1, h2, h3, h4, h5 { color: #01004c; } + +a { + color: red; + text-decoration: none; +} + +hr { + border: 0; + color: #9E9E9E; + background-color: #9E9E9E; + height: 1px; +} + +pre { + overflow-x: auto; + /* border: 3px solid #555; */ + /* color: #ddd; */ + /* background-color: #111; */ + /* padding: 2px; */ + line-height: 1.3em; +} \ No newline at end of file diff --git a/etest.el b/etest.el new file mode 100644 index 0000000..3522bfe --- /dev/null +++ b/etest.el @@ -0,0 +1,243 @@ +;;; etest.el --- Run tests and get back a hierarchical set of results. + +;; Copyright (C) 2008 Philip Jackson + +;; Author: Philip Jackson +;; Version: 0.1 + +;; This file is not currently part of GNU Emacs. + +;; 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 2, 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 this program ; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; etest lets you define tests in a domain specific, hierarchical +;; manner and gather results in a simple, structure of the same shape. + +;; To install you must put the location of etest into your +;; `load-path', perhaps like this: +;; +;; (add-to-list 'load-path "~/.elisp/etest") +;; +;; Then actually load etest.el: +;; +;; (require 'etest) +;; +;; If you want graphical feed back then just load `etest-result-mode' +;; like this: +;; +;; (require 'etest-result-mode) +;; +;; Valid examples of etest usage might be: +;; +;; Checking (+ 1 1) yeilds a non-nil result: +;; (etest '(ok (+ 1 1))) +;; +;; You can add an extra argument to the end of any test and it will be +;; used as the documentation string for the test: +;; +;; (etest '(ok (+ 1 1) "Check 1 + 1 yeilds non-nil")) +;; +;; If you omit this string then one will be generated for you. +;; +;; Checking (+ 1 1) yeilds 2: +;; (etest '(eq (+ 1 1) 2)) +;; +;; To combine these you might do this: +;; (etest '("Check '+' function" (ok (+ 1 1)) (eq (+ 1 1) 2))) +;; +;; The string is just a header to split things up and hopefully make +;; the output more readable. You can have header groups nest as deeply +;; as you like and within each as many tests as you like. +;; +;; To define your own tests the `deftest' function should be used. For +;; example the following can (and is) used to test etest itself: +;; +;; (deftest '(eres 1) +;; (lambda (test) +;; (etest-ok +;; (plist-get (car (etest-run (list test))) :result)))) +;; +;; Used like this: +;; +;; (etest '(eres (ok t))) +;; +;; I can see if etests 'built-ins' are working. + +(require 'etest-result-mode) + +(defvar etest-show-graphical-results t + "Choose whether a graphical representation of results should +pop-up in another window.") + +(defvar etest-candidates-plist + '(eq (etest-eq 2) + noerror (etest-noerror 1) + error (etest-error 1) + like (etest-like 2) + null (etest-null 1) + equal (etest-equal 2) + eql (etest-eql 2) + ok (etest-ok 1)) + "Plist of test candidates where PROP is the name of the new +test . See `deftest' for details of how to modify this.") + +(defun deftest (details func) + "Define a new test. DETAILS must be a list containing the name +of the test and the argcount. FUNC is the actual function that +will be run." + (let ((name (car details)) + (argcount (cadr details))) + (plist-put etest-candidates-plist + name (list func argcount)))) + +(defun etest-ok (test) + "Simply eval TEST and pass if the result is non-nil." + (let ((ret (eval test)) + (result '())) + (setq result (plist-put result :result (not (null ret)))) + (setq result (plist-put result :comments (format "got: '%S'" ret))) + result)) + +(defun etest-equality-test (func one two) + "Compare two items, ONE and TWO, using the function +FUNC. Returns a test result." + (let ((one (eval one)) + (two (eval two)) + (res (funcall func (eval one) (eval two))) + (result '())) + (setq result (plist-put result :result res)) + (setq result (plist-put result :comments + (if res + (format "both: '%S'" one) + (format "one: '%S'\ntwo: '%S'" one two)))))) + +(defun etest-null (test) + "Allows the use of `null' in a test." + (etest-ok `(null ,test))) + +(defun etest-eq (one two) + "Allows the use of `eq' in a test." + (etest-equality-test 'eq one two)) + +(defun etest-equal (one two) + "Allows the use of `equal' in a test." + (etest-equality-test 'equal one two)) + +(defun etest-eql (one two) + (etest-equality-test 'eql one two)) + +(defun etest-noerror (form) + "Assert FORM evals without error." + (let ((result (etest-error form))) + (plist-put result :result (not (plist-get result :result))))) + +(defun etest-error (form) + "Assert FORM evals with error." + (let* ((result '()) + (val (condition-case err (eval form) + (error + (setq result (list :result t + :comments (format "got: '%S'" err))))))) + (if result + result + (list :result nil + :comments (format "got: '%S'" val))))) + +(defun etest-resultp (result) + "Check that RESULT is a vaid test result." + (and (plist-member result :result) + (booleanp (plist-get result :result)) + (plist-member result :comments))) + +(defun etest-like (form re) + "Check string is like re" + (let* ((i 0) + (match nil) + (re (eval re)) + (string (eval form)) + (comments (concat "searching: '" string "'\n")) + (res (not (not (string-match re string)))) + (result (list :result res))) + (while (setq match (match-string (setq i (1+ i)) string)) + (setq comments (concat (or comments "") + (format "match %3d: '%s'\n" i match)))) + (plist-put result :comments comments) + result)) + +;;;###autoload +(defmacro etest (&rest form) + "Wrapper to `etest-run'. Will popup a window displaying the +results of the run." + `(let ((results (etest-run ',form))) + (when etest-show-graphical-results + (etest-rm-refresh-buffer results)) + results)) + +(defun etest-run (form) + "This function does all of the work where actually running the +tests is concerned. Takes a valid etest form and will return a +similarly shaped set of results. " + (mapcar + '(lambda (test) + (let ((name (car test))) + (cond + ((stringp name) + (cons name (etest-run (cdr test)))) + ((symbolp name) + (let ((cand (car (plist-get etest-candidates-plist name))) + (args (cdr test)) + (argcount (cadr (plist-get etest-candidates-plist name))) + (doc nil)) + (unless cand + (error "'%s' is not a valid name type" name)) + (if (< (length args) argcount) + (error "%s needs %d arguments" cand argcount) + (if (and (eq (length args) (1+ argcount)) + (stringp (car (last args)))) + (progn + (setq doc (car (last args))) + (setq args (delq doc args))) + (setq doc (prin1-to-string test)))) + (plist-put (apply cand args) :doc doc)))))) + form)) + +;; This is defined so that etest can test itself +(defun etest-test-tests (test result) + "This test is used to test ETest itself. TEST is the test to be +run (in ETest syntax) and RESULT is a plist of items you would +like to compare. See the file etest.etest for example usage." + (let ((testres (car (etest-run (list test)))) + (my-res t) + (my-comments "")) + (when (null (plist-get testres :result)) + (setq my-comments "note: test result was nil\n")) + (dolist (sym '(:result :comments :doc)) + (let ((testval (plist-get testres sym)) + (resultval (plist-get result sym))) + (when (and (plist-member result sym) + (not (equal testval resultval))) + (setq my-res nil) + (setq my-comments + (concat my-comments + (format "got: %S from '%S'\n" + resultval sym)))))) + (list :result my-res :comments my-comments))) + +;; Make `etest-test-tests' available +(deftest '(eres 2) 'etest-test-tests) + +(provide 'etest) diff --git a/etest.etest b/etest.etest new file mode 100644 index 0000000..29024f0 --- /dev/null +++ b/etest.etest @@ -0,0 +1,46 @@ +(etest + ("Etest" + ("Simple" + (ok 1) + (null nil) + (eq 1 1) + (eql 1.1 1.1) + (equal '(1 2) '(1 2)) + (error (/ 1 0)) + (noerror (+ 1 1)) + (like "Hello" "^\\(H\\).+\\(o\\)$")) + ("Results" + ("ok results" + (eres (ok nil) (:result nil)) + (eres (ok t) (:result t)) + (eres (ok (+ 1 1)) (:result t)) + (eres (ok 0) (:result t))) + ("eq results" + (eres (eq 1 1) (:result t)) + (eres (eq 1 2) (:result nil))) + ("equal results" + (eres (equal '(1 2) '(1 2)) (:result t)) + (eres (equal '(1 2) '(3 4)) (:result nil))) + ("error" + (eres (error (/ 1 0)) (:result t)) + (eres (error (/ 0 1)) (:result nil))) + ("noerror" + (eres (noerror (+ 1 1)) (:result t)) + (eres (noerror (/ 1 0)) (:result nil))) + ("Documentation" + ("Defined by user" + (eres (ok 1 "Foo") (:doc "Foo")) + (eres (eq 1 1 "Another doc") (:doc "Another doc"))) + ("Auto generated" + (eres (ok 1) (:doc "(ok 1)") + "Correct ok docs generated.") + (eres (eq 1 1) (:doc "(eq 1 1)") + "Correct eq docs generated."))) + ("Comments" + (eres (error (/ 1 0)) (:comments "got: '(arith-error)'") + "We get arith-error from a divide by 0") + (eres (ok "Something") (:comments "got: '\"Something\"'")) + (eres (eq 1 1) (:comments "both: '1'") + "eq hit") + (eres (eq 1 2) (:comments "one: '1'\ntwo: '2'") + "eq miss"))))) diff --git a/etest.texinfo b/etest.texinfo new file mode 100644 index 0000000..da1a9b9 --- /dev/null +++ b/etest.texinfo @@ -0,0 +1,395 @@ +\input texinfo @c -*-texinfo-*- + +@setfilename etest.info +@settitle Emacs Testing Framework + +@dircategory Emacs +@direntry +* ETest: (etest). The Emacs Testing Framework +@end direntry + +@copying +Copyright @copyright{} 2008 Philip Jackson. +@quotation +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.1 or +any later version published by the Free Software Foundation; with no +Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A +copy of the license is included in the section entitled "GNU Free +Documentation License". +@end quotation +@end copying + +@titlepage +@title ETest - The Emacs Testing Framework + +@page +@vskip 0pt plus 1filll +@insertcopying +@end titlepage + +@ifhtml +@contents +@end ifhtml + +@ifnottex +@node Top, Introduction, (dir), (dir) +@top ETest Manual 0.1 + +@menu +* Introduction:: Introduction to ETest. +* Installation:: How to get emacs to wear ETest. + +* Usage:: How to use ETest. +* The results buffer:: Display of test results. +* The Tests:: Functions available for testing. +@end menu +@end ifnottex + +@node Introduction, Installation, Top, Top +@chapter Introduction + +ETest (or etest, if you like) is the Emacs Testing Framework. It is a +small modular system for writing unit tests in Emacs Lisp. + +At time of writing ETest consists of two files. @file{etest.el} +provides the core test functionality. It defines the function +(actually, macro) @code{etest} which is usually ones entrance into a +test run. The file @file{etest-result-mode.el} adds functions that +allow the visualisation of a run with syntax highlighting, folding and +comment toggling. + +@node Installation +@chapter Installation + +To install ETest use one of the following methods: + +@section Compiling ETest + +Once you've unpacked the tarball change into the new directory and run +@code{make} if all goes well run @code{make install} (you may need +escalated privileges for this step). + +@section Manual Installation + +If you don't want to byte-compile for some reason then you can just +copy the @code{.el} files into a directory in your @var{load-path}. + +If you would rather add the unpacked library @emph{to} your load path +then the following will work assuming the etest directory is in your +home directory: + +@lisp +(add-to-list 'load-path "~/etest") +@end lisp + +@node Usage +@chapter Usage + +First you must evaluate @code{(require 'etest)} then you can start +using the @code{etest} macro. + +Using ETest is very simple. Once you have installed the modules then +you can simply run your first test like this: + +@lisp +(etest (ok 1)) +@end lisp +@noindent + +This should pop up a results buffer showing you the outcome of the +run. In this case all should be ok because, well, 1 is a pass +according to the @code{ok} test. + +@node The results buffer +@chapter The results buffer + +The results buffer is where you can see (and manipulate) the results +of a test run in a human friendly format. It will always popup when +etest is run and let you know how things went. + +@menu +* Example output:: What the output looks like. +* Bindings:: What commands you have at your disposal. +@end menu + +@node Example output +@section Example output + +Given the hypothetical tests: + +@lisp +(etest + ("Numeric comparisons" + ("Integers" + (ok (> 1 0) "one is more than 0") + (ok (< 1 0) "one is less than 0")) + ("Floats" + (ok (> 10 9.99)) + (ok (< 1 -5.2) "one is less than 5.2")))) +@end lisp +@noindent + +Once run give us the following in the results buffer: + +@example +* Numeric comparisons +** Integers + ok ................ one is more than 0 + # got: 't' + not ok ............ one is less than 0 + # got: 'nil' +** Floats + ok ................ (ok (> 10 9.99)) + # got: 't' + not ok ............ one is less than 5.2 + # got: 'nil' +@end example +@noindent + +All headings are foldable as are comments. + +@node Bindings +@section Bindings + +@table @kbd +@item q +@findex bury-buffer +Bury this results buffer. +@item # +@findex etest-rm-etest-rm-cycle-comments +Shift the values in @code{etest-rm-comment-visibility-types} and use +the @code{car} of that list to determine the visibility of comments. +@item +@findex etest-rm-toggle-headline +Toggle the visibility of a heading or test comment. +@end table + +@node The Tests +@chapter The Tests + +Tests are always run within the @code{etest} form and usually always +evaluate their arguments. Tests will always have a defined number of +required arguments (for example @code{ok} requires one argument. Each +test also allows for one optional argument which is a custom +documentation string. If this argument is omitted then ETest will +generate one in its place. So, for example, if you used +@code{(etest (ok 1))} the doc string would be @code{"(ok 1)"} if you used +@code{(etest (ok 1 "Foo"))} the doc string would be @code{"Foo"}. + +@menu +* Test Structure:: Basic structure of tests. +* Builtin Simple Tests:: Boolean checks. +* Builtin Equality Tests:: Are two things similar? +* Builtin Error Tests:: Test exception handling. +* Builtin String Tests:: +* Defining your own tests:: Extend ETest. +@end menu + +@node Test Structure +@section Test Structure + +Tests can be grouped within headings by simply using a string as the +first element of a form like this: + +@lisp +(etest + ("Simple tests" + (ok 1))) +@end lisp +@noindent + +You can nest headings to your hearts content. + +This will produce a set of results that follow the same hierarchical +pattern as the tests themselves. For example the above produces the +following structure: + +@lisp +(("Simple tests" + (:result t :comments "got: '1'" :doc "(ok 1)"))) +@end lisp +@noindent + +The output in the results buffer is: + +@example +* Simple tests + ok ................. (ok 1) + # got: '1' +@end example + +@node Builtin Simple Tests +@section Builtin Simple Tests + +These basic tests allow you, basically, to check if a value is either +non-nil or nil. + +@subsection ok + +@code{ok} will only pass if its argument produces a non-nil result. + +@lisp +(etest (ok (+ 1 1))) +@end lisp +@noindent + +@subsection null + +@code{null} will only pass if its argument produces a nil result. + +@lisp +(etest (null nil)) +@end lisp +@noindent + +@node Builtin Equality Tests +@section Builtin Equality Tests + +The following functions map to their lisp counterparts and so don't +really require much explanation. Evaluate each of the examples to +watch them pass. + +Each take two forms which, post evaluation, are the objects to +compare. + +@subsection eq + +@lisp +(etest (eq 1 1)) +@end lisp +@noindent + +@subsection eql + +@lisp +(etest (eql 1.1 1.1)) +@end lisp +@noindent + +@subsection equal + +@lisp +(etest (equal '(1 2) '(1 2))) +@end lisp +@noindent + +@node Builtin Error Tests +@section Builtin Error Tests + +These two functions each take one form. + +@subsection error + +This test will pass if an exception is raised. For example, cause a +divide by zero error (@code{(arith-error)}): + +@lisp +(etest (error (/ 1 0))) +@end lisp +@noindent + +@subsection noerror + +This test will pass if no exception is raised. For example, a valid +division will not raise an error: + +@lisp +(etest (noerror (/ 0 1))) +@end lisp +@noindent + +@node Builtin String Tests +@section Builtin String Tests + +@subsection like + +@code{like} takes two arguments a string and a regexp to test it against: + +@lisp +(etest (like "Hello" "^H\\(e\\)")) +@end lisp +@noindent + +Produces this in the results buffer: + +@example + ok .................. (like "Hello" "^H\\(e\\)") + # searching: 'Hello' + # match 1: 'e' +@end example +@noindent + +The grouping within the regular expression only affects the comments. + +@node Defining your own tests +@section Defining your own tests + +Defining your own tests is fairly trivial and where ETest becomes +really useful. + +Each test must return a plist that has @code{:result} and, optionally, +@code{:comments} in it. + +@code{:result} represents whether the test passed or failed non-nil +for a pass and nil for a fail. + +The optional @code{:comments} are newline separated strings that might +help the user in their diagnosis of a problem. Comments should follow +the conventions set by the 'builtin' tests using keywords such as +'got:'. + +To have ETest recognise the test as valid the @code{deftest} function +should be used. For example if I wanted to create a function that +took two arguments and tested the first was numerically greater than +the other I might do this: + +@lisp +(defun etest-greater-than (one two) + (let* ((res (> one two))) + (list :result res + :comments (unless res + (format "one: '%d'\ntwo: '%d'" one two))))) +@end lisp +@noindent + +We let emacs take care of type errors (and any other type of error) +which is what all of the builtins do. Also it's worth noting that if +you want your arguments evaling you have to do it yourself. + +Now we let ETest know this new function exists: + +@lisp +(deftest '(> 2) 'etest-greater-than) +@end lisp +@noindent + +So, the new function will be called @code{>}, it will take two arguments +and it calls maps to @code{etest-greater-than}. + +@lisp +(deftest '(> 2) 'etest-greater-than) +@end lisp +@noindent + +Now you can mix @code{>} with other tests: + +@lisp +(etest + (ok "something") + (> 1 2 "one is more than two")) +@end lisp +@noindent + +Which in the results buffer produces: + +@example + ok .................. (ok "something") + # got: '"something"' + not ok .............. one is more than two + # one: '1' + # two: '2' +@end example +@noindent + +@bye diff --git a/todo.org b/todo.org new file mode 100644 index 0000000..a9bd0b7 --- /dev/null +++ b/todo.org @@ -0,0 +1,14 @@ +* Etest +** ETest +*** TODO Make the function which presents the results customisable. +** Result Mode +*** TODO Use the regexps defined for font-locking +*** Toggling +**** TODO [2/3] Toggle all comments + - [X] All comments + - [X] 'not ok' comments + - [ ] Keybindings +**** TODO [0/1] Toggle all headlines + - [ ] With a numeric prefix for level +**** TODO Toggle headlines with no failures +**** TODO Rotate through the display of 'not ok' 'ok' and 'both' -- 2.11.4.GIT