summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2021-01-17 11:21:35 -0700
committerSean Whitton <spwhitton@spwhitton.name>2021-01-17 11:21:35 -0700
commit8c99f26f472d8aa3982b299f0994ad3a5ebe683f (patch)
tree07ebcccb0194947e416fc6ece275c290f1d28146
parent2d0619ebd79ba953050ea68ea5187c285ff3682b (diff)
parent1de6be465cfe2c3f00183de9351bd838690c9f81 (diff)
Merge tag 'v1.24'
Bump version: 1.23 → 1.24
-rw-r--r--.bumpversion.cfg2
-rw-r--r--.github/workflows/test.yml41
-rw-r--r--.travis.yml17
-rw-r--r--Makefile4
-rwxr-xr-xbin/buttercup46
-rw-r--r--bin/buttercup.bat46
-rw-r--r--buttercup-compat.el11
-rw-r--r--buttercup-pkg.el2
-rw-r--r--buttercup.el529
-rw-r--r--docs/running-tests.md91
-rw-r--r--tests/test-buttercup.el774
11 files changed, 1218 insertions, 345 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index a8621c9..94fa0fe 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 1.22
+current_version = 1.24
parse = (?P<major>\d+)\.(?P<minor>.*)
serialize = {major}.{minor}
files = buttercup.el buttercup-pkg.el
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..7906981
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,41 @@
+name: Build and test
+
+on:
+ pull_request:
+ push:
+ branches:
+ - 'master'
+ - '*'
+ paths-ignore:
+ - 'bin/*'
+ - 'docs/images/*'
+ - 'docs/running-tests.md'
+ - 'scripts'
+
+jobs:
+ build:
+ name: Build and test
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ emacs_version:
+ - 24.3
+ - 24.4
+ - 24.5
+ - 25.1
+ - 25.2
+ - 25.3
+ - 26.1
+ - 26.2
+ - 26.3
+ - 27.1
+ - snapshot
+ steps:
+ - uses: purcell/setup-emacs@master
+ with:
+ version: ${{ matrix.emacs_version }}
+
+ - uses: actions/checkout@v2
+ - name: Run tests
+ run: make test
+
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 74c768e..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-language: emacs-lisp
-dist: xenial
-env:
- - EVM_EMACS=emacs-24.3-travis
- - EVM_EMACS=emacs-24.4-travis
- - EVM_EMACS=emacs-24.5-travis
- - EVM_EMACS=emacs-25.1-travis
- - EVM_EMACS=emacs-25.2-travis
- - EVM_EMACS=emacs-26.1-travis-linux-xenial
- - EVM_EMACS=emacs-26.2-travis-linux-xenial
- - EVM_EMACS=emacs-26.3-travis-linux-xenial
-before_install:
- - sudo apt-get install libgnutls30
- - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > travis.sh && source ./travis.sh
- - evm install "$EVM_EMACS" --use --skip
-script:
- - make test
diff --git a/Makefile b/Makefile
index 58aac21..276f24c 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ all: test
test: test-buttercup test-docs
test-buttercup: compile
- ./bin/buttercup -L . tests
+ ./bin/buttercup -L . tests $(if $(CI),--traceback pretty)
test-docs: compile
$(EMACS) -batch -L . -l buttercup.el -f buttercup-run-markdown docs/writing-tests.md
@@ -25,4 +25,4 @@ release: clean test
tar -c $(DISTFILES) --transform "s,^,buttercup-$(VERSION)/," --transform 's/README.md/README.txt/' > "dist/buttercup-$(VERSION).tar"
clean:
- rm -f *.elc
+ rm -f *.elc tests/*.elc
diff --git a/bin/buttercup b/bin/buttercup
index 606436b..2230e84 100755
--- a/bin/buttercup
+++ b/bin/buttercup
@@ -29,22 +29,37 @@ Buttercup options:
--pattern, -p PATTERN Only run tests with names matching PATTERN.
This option can be used multiple times, in
which case tests will be run if they match
- any of the given patterns.
+ any of the given patterns. PATTERN should be
+ an Emacs regex that will be matched against
+ the full test description (the concatenation
+ of the test and all parent suites
+ descriptions).
+
+--no-skip Do not print the descriptions for tests that
+ are filtered out with "--pattern" or disabled
+ with "xit". Tests skipped wiath "assume" will
+ still be priuted,
+
+--only-error Only print failed tests and their containing suites.
+ Implies "--no-skip".
--no-color, -c Do not colorize test output.
--traceback STYLE When printing backtraces for errors that occur
during tests, print them in the chosen
- STYLE. Available styles are "full", which
- shows the full function call for each stack
- frame on a single line, "crop", which
- truncates each stack frame to 80 characters
- (the default), and "pretty", which uses
- Emacs' pretty-printing facilities to print
- each stack frame, and also annotates each
- frame with a lambda or M to indicate whether
- it is a normal function call or a
- macro/special form.
+ STYLE. Available styles are
+ "full", which shows the full function call for
+ each stack frame on a single line,
+ "crop", which truncates each stack frame to 80
+ characters (the default),
+ "pretty", which uses Emacs' pretty-printing
+ facilities to print each stack frame, and also
+ annotates each frame with a lambda or M to
+ indicate whether it is a normal function call
+ or a macro/special form and
+ "omit", which omits the backtraces alltogether.
+
+--stale-file-error Fail the test run if stale .elc files are loaded.
EOF
}
@@ -70,7 +85,7 @@ do
shift
shift
;;
- "-c"|"--no-color")
+ "-c"|"--no-color"|"--no-skip"|"--only-error"|"--stale-file-error")
BUTTERCUP_ARGS+=("$1")
shift
;;
@@ -87,4 +102,9 @@ do
esac
done
-exec "$EMACS_BIN" -batch "${EMACS_ARGS[@]}" -l buttercup -f buttercup-run-discover "${BUTTERCUP_ARGS[@]}"
+# `--' is needed so that Buttercup options don't get parsed by Emacs itself.
+exec "$EMACS_BIN" -batch "${EMACS_ARGS[@]}" -l buttercup -f buttercup-run-discover -- "${BUTTERCUP_ARGS[@]}"
+
+# Local Variables:
+# indent-tabs-mode: nil
+# End:
diff --git a/bin/buttercup.bat b/bin/buttercup.bat
index 092230a..108c910 100644
--- a/bin/buttercup.bat
+++ b/bin/buttercup.bat
@@ -33,22 +33,37 @@ echo.
echo --pattern, -p PATTERN Only run tests with names matching PATTERN.
echo This option can be used multiple times, in
echo which case tests will be run if they match
-echo any of the given patterns.
+echo any of the given patterns. PATTERN should be
+echo an Emacs regex that will be matched against
+echo the full test description (the concatenation
+echo of the test and all parent suites
+echo descriptions).
+echo.
+echo --no-skip Do not print the descriptions for tests that
+echo are filtered out with "--pattern" or disabled
+echo with "xit". Tests skipped wiath "assume" will
+echo still be priuted,
+echo.
+echo --only-error Only print failed tests and their containing suites.
+echo Implies "--no-skip".
echo.
echo --no-color, -c Do not colorize test output.
echo.
echo --traceback STYLE When printing backtraces for errors that occur
echo during tests, print them in the chosen
-echo STYLE. Available styles are "full", which
-echo shows the full function call for each stack
-echo frame on a single line, "crop", which
-echo truncates each stack frame to 80 characters
-echo ^(the default^), and "pretty", which uses
-echo Emacs' pretty-printing facilities to print
-echo each stack frame, and also annotates each
-echo frame with a lambda or M to indicate whether
-echo it is a normal function call or a
-echo macro/special form.
+echo STYLE. Available styles are
+echo "full", which shows the full function call for
+echo each stack frame on a single line,
+echo "crop", which truncates each stack frame to 80
+echo characters (the default),
+echo "pretty", which uses Emacs' pretty-printing
+echo facilities to print each stack frame, and also
+echo annotates each frame with a lambda or M to
+echo indicate whether it is a normal function call
+echo or a macro/special form and
+echo "omit", which omits the backtraces alltogether.
+echo.
+echo --stale-file-error Fail the test run if stale .elc files are loaded.
exit /b
:parse_args
@@ -110,6 +125,15 @@ if not [%current_arg%]==[] (
) else if !current_arg!==--no-color (
set buttercup_args=!buttercup_args! !current_arg!
shift /1
+ ) else if !current_arg!==--no-skip (
+ set buttercup_args=!buttercup_args! !current_arg!
+ shift /1
+ ) else if !current_arg!==--only-error (
+ set buttercup_args=!buttercup_args! !current_arg!
+ shift /1
+ ) else if !current_arg!==--stale-file-error (
+ set buttercup_args=!buttercup_args! !current_arg!
+ shift /1
) else if !current_arg!==--traceback (
set buttercup_args=!buttercup_args! !current_arg! !next_arg!
shift /1
diff --git a/buttercup-compat.el b/buttercup-compat.el
index d70bd62..792a8c8 100644
--- a/buttercup-compat.el
+++ b/buttercup-compat.el
@@ -109,6 +109,17 @@ If INCLUDE-DIRECTORIES, also include directories that have matching names."
(<= (car here) delay)))
(concat (format "%.2f" (/ delay (car (cddr here)))) (cadr here)))))))
+;;;;;;;;;;;;;;;;;;;;;
+;; Introduced in 26.1
+
+(unless (fboundp 'file-attribute-modification-time)
+ (defsubst file-attribute-modification-time (attributes)
+ "The modification time in ATTRIBUTES returned by `file-attributes'.
+This is the time of the last change to the file's contents, and
+is a Lisp timestamp in the style of `current-time'."
+ (nth 5 attributes)))
+
+
(provide 'buttercup-compat)
;;; buttercup-compat.el ends here
diff --git a/buttercup-pkg.el b/buttercup-pkg.el
index 5b763cb..5d92605 100644
--- a/buttercup-pkg.el
+++ b/buttercup-pkg.el
@@ -1,2 +1,2 @@
-(define-package "buttercup" "1.22"
+(define-package "buttercup" "1.24"
"Behavior-Driven Emacs Lisp Testing")
diff --git a/buttercup.el b/buttercup.el
index 535aa05..0c2f325 100644
--- a/buttercup.el
+++ b/buttercup.el
@@ -2,7 +2,7 @@
;; Copyright (C) 2015-2017 Jorgen Schaefer <contact@jorgenschaefer.de>
-;; Version: 1.22
+;; Version: 1.24
;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>
;; Package-Requires: ((emacs "24.3"))
;; URL: https://github.com/jorgenschaefer/emacs-buttercup
@@ -56,7 +56,9 @@
The function MUST have one of the following forms:
\(lambda () EXPR)
+\(lambda () (buttercup--mark-stackframe) EXPR)
\(closure (ENVLIST) () EXPR)
+\(closure (ENVLIST) () (buttercup--mark-stackframe) EXPR)
\(lambda () (quote EXPR) EXPR)
\(closure (ENVLIST) () (quote EXPR) EXPR)
@@ -65,12 +67,14 @@ forms are useful if EXPR is a macro call, in which case the
`quote' ensures access to the un-expanded form."
(pcase fun
(`(closure ,(pred listp) nil ,expr) expr)
+ (`(closure ,(pred listp) nil (buttercup--mark-stackframe) ,expr) expr)
(`(closure ,(pred listp) nil (quote ,expr) . ,_rest) expr)
(`(closure ,(pred listp) nil ,_expr . ,(pred identity))
(error "Closure contains multiple expressions: %S" fun))
(`(closure ,(pred listp) ,(pred identity) . ,(pred identity))
(error "Closure has nonempty arglist: %S" fun))
(`(lambda nil ,expr) expr)
+ (`(lambda nil (buttercup--mark-stackframe) ,expr) expr)
(`(lambda nil (quote ,expr) . ,_rest) expr)
(`(lambda nil ,_expr . ,(pred identity))
(error "Function contains multiple expressions: %S" fun))
@@ -132,9 +136,16 @@ This macro knows three forms:
\(expect ARG)
Fail the current test if ARG is not true."
(let ((wrapped-args
- (mapcar (lambda (expr) `(lambda () (quote ,expr) ,expr)) args)))
+ (mapcar (lambda (expr) `(lambda ()
+ (quote ,expr)
+ (buttercup--mark-stackframe)
+ ,expr))
+ args)))
`(buttercup-expect
- (lambda () (quote ,arg) ,arg)
+ (lambda ()
+ (quote ,arg)
+ (buttercup--mark-stackframe)
+ ,arg)
,(or matcher :to-be-truthy)
,@wrapped-args)))
@@ -632,10 +643,10 @@ See also `buttercup-define-matcher'."
(cond
((not calls)
(cons nil
- (format "Expected `%s' to have been called with %s, but it was not called at all" spy args)))
+ (format "Expected `%s' to have been called with %S, but it was not called at all" spy args)))
((not (member args calls))
(cons nil
- (format "Expected `%s' to have been called with %s, but it was called with %s"
+ (format "Expected `%s' to have been called with %S, but it was called with %s"
spy
args
(mapconcat (lambda (args)
@@ -801,6 +812,18 @@ The indentaion is two spaces per parent."
(let ((level (length (buttercup-suite-or-spec-parents suite-or-spec))))
(concat (make-string (* 2 level) ?\s) (buttercup-suite-or-spec-description suite-or-spec))))
+(defun buttercup--spec-mark-pending (spec description &optional description-for-now)
+ "Mark SPEC as pending with DESCRIPTION.
+If DESCRIPTION-FOR-NOW is non-nil, set the spec
+`pending-description' to that value for now, it will be reset to
+DESCRIPTION when the spec is run. Return SPEC."
+ (setf (buttercup-spec-function spec)
+ (lambda () (signal 'buttercup-pending description))
+ (buttercup-spec-status spec) 'pending)
+ (when description-for-now
+ (setf (buttercup-spec-failure-description spec) description-for-now))
+ spec)
+
;;;;;;;;;;;;;;;;;;;;
;;; Suites: describe
@@ -873,6 +896,7 @@ most probably including one or more calls to `expect'."
`(buttercup-it ,description
(lambda ()
(buttercup-with-converted-ert-signals
+ (buttercup--mark-stackframe)
,@body)))
`(buttercup-xit ,description)))
@@ -1022,15 +1046,8 @@ A disabled spec is not run.
DESCRIPTION has the same meaning as in `xit'. FUNCTION is
ignored. Return the created spec object."
(declare (indent 1))
- (ignore function)
- (let ((spec (buttercup-it description
- (lambda ()
- (signal 'buttercup-pending "PENDING")))))
- (setf (buttercup-spec-status spec)
- 'pending
- (buttercup-spec-failure-description spec)
- "")
- spec))
+ (let ((spec (buttercup-it description (or function #'ignore))))
+ (buttercup--spec-mark-pending spec "PENDING" "")))
;;;;;;;;;
;;; Spies
@@ -1060,7 +1077,9 @@ CURRENT-BUFFER is the buffer that was current when the spy was called.
RETURN-VALUE is the returned value, if any.
THROWN-SIGNAL is the signal raised by the function, if any.
Only one of RETURN-VALUE and THROWN-SIGNAL may be given. Giving
-none of them is equivalent to `:return-value nil'."
+none of them is equivalent to `:return-value nil'.
+
+\(fn &key ARGS CURRENT-BUFFER RETURN-VALUE THROWN-SIGNAL)"
(cond
((and has-return-value has-thrown-signal)
(error "Only one of :return-value and :thrown-signal may be given"))
@@ -1306,9 +1325,10 @@ spec-started -- A spec in is starting. The argument is the spec.
spec-done -- A spec has finished executing. The argument is the
spec.
-suite-done -- A suite has finished. The argument is the spec.
+suite-done -- A suite has finished. The argument is the suite.
-buttercup-done -- All suites have run, the test run is over.")
+buttercup-done -- All suites have run, the test run is over. The
+ argument is the list of executed suites.")
(defvar buttercup-stack-frame-style (car '(crop full pretty))
"Style to use when printing stack traces of tests.
@@ -1330,6 +1350,9 @@ or a macro/special form.")
A buffer with this name should only exist while running a test
spec, and should be killed after running the spec.")
+;; predeclaration
+(defvar buttercup-reporter-batch-quiet-statuses)
+
;;;###autoload
(defun buttercup-run-at-point ()
"Run the buttercup suite at point."
@@ -1352,6 +1375,8 @@ current directory."
(args command-line-args-left))
(while args
(cond
+ ((equal (car args) "--")
+ (setq args (cdr args)))
((member (car args) '("--traceback"))
(when (not (cdr args))
(error "Option requires argument: %s" (car args)))
@@ -1368,6 +1393,17 @@ current directory."
((member (car args) '("-c" "--no-color"))
(setq buttercup-color nil)
(setq args (cdr args)))
+ ((equal (car args) "--no-skip")
+ (push 'skipped buttercup-reporter-batch-quiet-statuses)
+ (push 'disabled buttercup-reporter-batch-quiet-statuses)
+ (setq args (cdr args)))
+ ((equal (car args) "--only-error")
+ (push 'pending buttercup-reporter-batch-quiet-statuses)
+ (push 'passed buttercup-reporter-batch-quiet-statuses)
+ (setq args (cdr args)))
+ ((equal (car args) "--stale-file-error")
+ (buttercup-error-on-stale-elc)
+ (setq args (cdr args)))
(t
(push (car args) dirs)
(setq args (cdr args)))))
@@ -1375,23 +1411,43 @@ current directory."
(dolist (dir (or dirs '(".")))
(dolist (file (directory-files-recursively
dir "\\`test-.*\\.el\\'\\|-tests?\\.el\\'"))
+ ;; Exclude any hidden directory, both immediate (^.) and nested (/.) subdirs
(when (not (string-match "\\(^\\|/\\)\\." (file-relative-name file)))
(load file nil t))))
(when patterns
- (buttercup--mark-skipped buttercup-suites patterns))
+ (buttercup-mark-skipped patterns t))
(buttercup-run)))
-(defun buttercup--mark-skipped (suites patterns)
- "Mark any spec in SUITES not matching PATTERNS as skipped.
-SUITES is a list of suites. PATTERNS is a list of regexps."
+(defun buttercup-mark-skipped (matcher &optional reverse)
+ "Mark any spec that match MATCHER as skipped.
+MATCHER can be either a regex, a list of regexes, or a function
+taking a spec as the single argument. If REVERSE is non-nil,
+specs will be marked as pending when MATCHER does not match."
+ (cl-etypecase matcher
+ (string (buttercup--mark-skipped
+ buttercup-suites
+ (lambda (spec)
+ (string-match matcher (buttercup-spec-full-name spec)))
+ reverse))
+ (function (buttercup--mark-skipped buttercup-suites matcher reverse))
+ (list (cond
+ ((cl-every #'stringp matcher)
+ (buttercup-mark-skipped (mapconcat (lambda (re)
+ (concat "\\(?:" re "\\)"))
+ matcher "\\|")
+ reverse))
+ (t (error "Bad matcher list: %s, should be list of strings" matcher))))))
+
+(defun buttercup--mark-skipped (suites predicate &optional reverse-predicate)
+ "Mark all specs in SUITES as skipped if PREDICATE(spec) is true.
+If REVERSE-PREDICATE is non-nil, mark spec where PREDICATE(spec)
+is false."
(dolist (spec (buttercup--specs suites))
- (let ((spec-full-name (buttercup-spec-full-name spec)))
- (unless (cl-dolist (p patterns)
- (when (string-match p spec-full-name)
- (cl-return t)))
- (setf (buttercup-spec-function spec)
- (lambda () (signal 'buttercup-pending "SKIPPED"))
- (buttercup-spec-status spec) 'pending)))))
+ ;; cond implements (xor reverse-predicate (funcall predicate
+ ;; spec)) as xor is introduced in Emacs 27
+ (when (cond ((not reverse-predicate) (funcall predicate spec))
+ ((not (funcall predicate spec)) reverse-predicate))
+ (buttercup--spec-mark-pending spec "SKIPPED"))))
;;;###autoload
(defun buttercup-run-markdown-buffer (&rest markdown-buffers)
@@ -1440,16 +1496,27 @@ A suite must be defined within a Markdown \"lisp\" code block."
;; Defined below in a dedicated section
(defvar buttercup-reporter))
-(defun buttercup-run ()
- "Run all described suites."
+(defun buttercup-run (&optional noerror)
+ "Run all described suites.
+Signal an error if any spec fail or if no suites have been
+defined. Signal no errors if NOERROR is non-nil. Return t if all
+specs pass, nil if at least one spec fail, and :no-suites if no suites
+have been defined."
(if buttercup-suites
- (progn
- (funcall buttercup-reporter 'buttercup-started buttercup-suites)
- (mapc #'buttercup--run-suite buttercup-suites)
- (funcall buttercup-reporter 'buttercup-done buttercup-suites)
- (when (> (buttercup-suites-total-specs-failed buttercup-suites) 0)
- (error "")))
- (error "No suites defined")))
+ (buttercup--run-suites buttercup-suites noerror)
+ (or (and noerror :no-suites)
+ (error "No suites defined"))))
+
+(defun buttercup--run-suites (suites &optional noerror)
+ "Run a list of SUITES.
+Signal an error if any spec fail. Signal no error if NOERROR is
+non-nil. Return t if all specs pass, nil if at least one spec
+fail."
+ (funcall buttercup-reporter 'buttercup-started suites)
+ (mapc #'buttercup--run-suite suites)
+ (funcall buttercup-reporter 'buttercup-done suites)
+ (or (zerop (buttercup-suites-total-specs-failed suites))
+ (not (or noerror (error "")))))
(defvar buttercup--before-each nil
"A list of functions to call before each spec.
@@ -1483,6 +1550,7 @@ Do not change the global value.")
(funcall buttercup-reporter 'suite-done suite)))
(defun buttercup--run-spec (spec)
+ "Run SPEC."
(buttercup--set-start-time spec)
(unwind-protect
(progn
@@ -1558,9 +1626,7 @@ Calls either `buttercup-reporter-batch' or
EVENT and ARG are described in `buttercup-reporter'."
(if noninteractive
- (if buttercup-color
- (buttercup-reporter-batch-color event arg)
- (buttercup-reporter-batch event arg))
+ (buttercup-reporter-batch event arg)
(buttercup-reporter-interactive event arg)))
(defvar buttercup-reporter-batch--start-time nil
@@ -1569,6 +1635,34 @@ EVENT and ARG are described in `buttercup-reporter'."
(defvar buttercup-reporter-batch--failures nil
"List of failed specs of the current batch report.")
+(defvar buttercup-reporter-batch-quiet-statuses nil
+ "Do not print results for any spec with any of the listed statuses.")
+
+(defvar buttercup-reporter-batch--suite-stack nil
+ "Stack of unprinted suites.")
+
+(defun buttercup-reporter-batch--quiet-spec-p (spec)
+ "Return non-nil if the status of SPEC is any of the quiet statuses.
+SPEC is considered quiet if its status is listed in
+`buttercup-reporter-batch-quiet-statuses'.
+
+Two special statuses can be listed in
+`buttercup-reporter-batch-quiet-statuses';
+ `skipped': Real spec status `pending' and failure description \"SKIPPED\".
+ This matches specs filtered out with `buttercup-mark-skipped'.
+ `disabled': Real spec status `pending' and failure description \"PENDING\".
+ This matches specs disabled with `xit' or equivalent."
+ (or (memq (buttercup-spec-status spec) buttercup-reporter-batch-quiet-statuses)
+ ;; check for the virtual status `skipped'
+ (and (memq 'skipped buttercup-reporter-batch-quiet-statuses)
+ (eq (buttercup-spec-status spec) 'pending)
+ (string= (buttercup-spec-failure-description spec) "SKIPPED"))
+ ;; check for the virtual status `disabled'
+ (and (memq 'disabled buttercup-reporter-batch-quiet-statuses)
+ (eq (buttercup-spec-status spec) 'pending)
+ (string= (buttercup-spec-failure-description spec) "PENDING"))
+ ))
+
(defun buttercup-reporter-batch (event arg)
"A reporter that handles batch sessions.
@@ -1578,7 +1672,8 @@ EVENT and ARG are described in `buttercup-reporter'."
(pcase event
(`buttercup-started
(setq buttercup-reporter-batch--start-time (current-time)
- buttercup-reporter-batch--failures nil)
+ buttercup-reporter-batch--failures nil
+ buttercup-reporter-batch--suite-stack nil)
(let ((defined (buttercup-suites-total-specs-defined arg))
(pending (buttercup-suites-total-specs-pending arg)))
(if (> pending 0)
@@ -1588,158 +1683,143 @@ EVENT and ARG are described in `buttercup-reporter'."
(buttercup--print "Running %s specs.\n\n" defined))))
(`suite-started
- (buttercup--print "%s\n" (buttercup--indented-description arg)))
+ (if buttercup-reporter-batch-quiet-statuses
+ (push arg buttercup-reporter-batch--suite-stack)
+ (buttercup--print "%s\n" (buttercup--indented-description arg))))
(`spec-started
- (buttercup--print "%s" (buttercup--indented-description arg)))
+ (or buttercup-reporter-batch-quiet-statuses
+ (and buttercup-color
+ (string-match-p "[\n\v\f]" (buttercup-spec-description arg)))
+ (buttercup--print "%s" (buttercup--indented-description arg))))
(`spec-done
- (cond
- ((eq (buttercup-spec-status arg) 'passed)) ; do nothing
- ((eq (buttercup-spec-status arg) 'failed)
- (buttercup--print " FAILED")
+ (when (and buttercup-reporter-batch-quiet-statuses
+ (not (buttercup-reporter-batch--quiet-spec-p arg)))
+ (dolist (suite (nreverse buttercup-reporter-batch--suite-stack))
+ (buttercup--print "%s\n" (buttercup--indented-description suite)))
+ (setq buttercup-reporter-batch--suite-stack nil)
+ (buttercup--print "%s" (buttercup--indented-description arg)))
+
+ (unless (buttercup-reporter-batch--quiet-spec-p arg)
+ (buttercup-reporter-batch--print-spec-done-line arg buttercup-color))
+
+ (when (eq (buttercup-spec-status arg) 'failed)
(setq buttercup-reporter-batch--failures
(append buttercup-reporter-batch--failures
- (list arg))))
- ((eq (buttercup-spec-status arg) 'pending)
- (buttercup--print " %s" (buttercup-spec-failure-description arg)))
- (t
- (error "Unknown spec status %s" (buttercup-spec-status arg))))
- (buttercup--print " (%s)\n" (buttercup-elapsed-time-string arg)))
+ (list arg)))))
(`suite-done
(when (= 0 (length (buttercup-suite-or-spec-parents arg)))
- (buttercup--print "\n")))
+ (if buttercup-reporter-batch-quiet-statuses
+ (unless buttercup-reporter-batch--suite-stack
+ (buttercup--print "\n"))
+ (buttercup--print "\n")))
+ (pop buttercup-reporter-batch--suite-stack))
(`buttercup-done
(dolist (failed buttercup-reporter-batch--failures)
- (let ((description (buttercup-spec-failure-description failed))
- (stack (buttercup-spec-failure-stack failed)))
- (buttercup--print "%s\n" (make-string 40 ?=))
- (buttercup--print "%s\n" (buttercup-spec-full-name failed))
- (when stack
- (buttercup--print "\nTraceback (most recent call last):\n")
- (dolist (frame stack)
- (let ((frame-text (buttercup--format-stack-frame frame)))
- (buttercup--print "%s\n" frame-text))))
- (cond
- ((stringp description)
- (buttercup--print "FAILED: %s\n" description))
- ((eq (car description) 'error)
- (buttercup--print "%S: %S\n\n"
- (car description)
- (cadr description)))
- (t
- (buttercup--print "FAILED: %S\n" description)))
- (buttercup--print "\n")))
- (let ((defined (buttercup-suites-total-specs-defined arg))
- (pending (buttercup-suites-total-specs-pending arg))
- (failed (buttercup-suites-total-specs-failed arg))
- (duration (float-time (time-subtract
- (current-time)
- buttercup-reporter-batch--start-time))))
- (if (> pending 0)
- (buttercup--print
- "Ran %s out of %s specs, %s failed, in %s.\n"
- (- defined pending)
- defined
- failed
- (seconds-to-string duration))
- (buttercup--print "Ran %s specs, %s failed, in %s.\n"
- defined
- failed
- (seconds-to-string duration)))))
+ (buttercup-reporter-batch--print-failed-spec-report failed buttercup-color))
+ (buttercup-reporter-batch--print-summary arg buttercup-color))
(_
(error "Unknown event %s" event)))))
-(defun buttercup-reporter-batch-color (event arg)
- "A reporter that handles batch sessions.
-
-Compared to `buttercup-reporter-batch', this reporter uses
-colors.
-
-EVENT and ARG are described in `buttercup-reporter'."
- (pcase event
- (`spec-started
- (unless (string-match-p "[\n\v\f]" (buttercup-spec-description arg))
- (buttercup-reporter-batch event arg)))
- (`spec-done
- ;; Carriage returns (\r) should not be colorized. It would mess
- ;; up color handling in Emacs compilation buffers using
- ;; `ansi-color-apply-on-region' in `compilation-filter-hook'.
- (pcase (buttercup-spec-status arg)
- (`passed
- (buttercup--print
- "\r%s" (buttercup-colorize (buttercup--indented-description arg) 'green)))
- (`failed
- (buttercup--print
- "\r%s" (buttercup-colorize
- (concat (buttercup--indented-description arg) " FAILED")
- 'red))
- (setq buttercup-reporter-batch--failures
- (append buttercup-reporter-batch--failures
- (list arg))))
- (`pending
- (if (equal (buttercup-spec-failure-description arg) "SKIPPED")
- (buttercup--print " %s" (buttercup-spec-failure-description arg))
- (buttercup--print
- "\r%s" (buttercup-colorize
- (concat (buttercup--indented-description arg) " "
- (buttercup-spec-failure-description arg))
- 'yellow))))
- (_
- (error "Unknown spec status %s" (buttercup-spec-status arg))))
- (buttercup--print " (%s)\n" (buttercup-elapsed-time-string arg)))
-
- (`buttercup-done
- (dolist (failed buttercup-reporter-batch--failures)
- (let ((description (buttercup-spec-failure-description failed))
- (stack (buttercup-spec-failure-stack failed)))
- (buttercup--print "%s\n" (make-string 40 ?=))
- (buttercup--print (buttercup-colorize "%s\n" 'red) (buttercup-spec-full-name failed))
- (when stack
- (buttercup--print "\nTraceback (most recent call last):\n")
- (dolist (frame stack)
- (let ((frame-text (buttercup--format-stack-frame frame)))
- (buttercup--print "%s\n" frame-text))))
- (cond
- ((stringp description)
- (buttercup--print (concat (buttercup-colorize "FAILED" 'red ) ": %s\n")
- description))
- ((eq (car description) 'error)
- (buttercup--print "%S: %S\n\n"
- (car description)
- (cadr description)))
- (t
- (buttercup--print "FAILED: %S\n" description)))
- (buttercup--print "\n")))
- (let ((defined (buttercup-suites-total-specs-defined arg))
- (pending (buttercup-suites-total-specs-pending arg))
- (failed (buttercup-suites-total-specs-failed arg))
- (duration (float-time (time-subtract (current-time)
- buttercup-reporter-batch--start-time))))
- (if (> pending 0)
- (buttercup--print
- (concat
- "Ran %s out of %s specs,"
- (buttercup-colorize " %s failed" (if (eq 0 failed) 'green 'red))
- ", in %s.\n")
- (- defined pending)
- defined
- failed
- (seconds-to-string duration))
- (buttercup--print
- (concat
- "Ran %s specs,"
- (buttercup-colorize " %s failed" (if (eq 0 failed) 'green 'red))
- ", in %s.\n")
- defined
- failed
- (seconds-to-string duration)))))
-
- (_
- ;; Fall through to buttercup-reporter-batch implementation.
- (buttercup-reporter-batch event arg)))
- )
+(defun buttercup-reporter-batch--print-spec-done-line (spec color)
+ "Print the remainder of the SPEC report line for `spec-done'.
+
+If COLOR is non-nil, erace the text so far on the current line
+using '\r' and replace it with the same text colored according to
+the SPEC status. Do not erase and replace if the text would have
+been reprinted with the default color.
+
+Then print the SPEC failure description except if the status is
+`passed'. If COLOR is non-nil, print it in the aproprate color
+for the spec status.
+
+Finally print the elapsed time for SPEC."
+ (let* ((status (buttercup-spec-status spec))
+ (failure (buttercup-spec-failure-description spec)))
+ ;; Failed specs do typically not have string filure-descriptions.
+ ;; In this typical case, use the string "FAILED" for the output.
+ (and (eq status 'failed)
+ (not (stringp failure))
+ (setq failure "FAILED"))
+ (unless (memq status '(passed pending failed))
+ (error "Unknown spec status %s" status))
+ ;; Special status in this function;
+ ;; skipped - a pending spec with failure description "SKIPPED".
+ (and (eq status 'pending)
+ (equal failure "SKIPPED")
+ (setq status 'skipped))
+ ;; Use color both as a boolean for erase-and-reprint and the color
+ ;; to use. nil means the default color.
+ (setq color (and color (pcase status
+ (`passed 'green)
+ (`pending 'yellow)
+ (`failed 'red)
+ (`skipped nil))))
+ (when color
+ ;; Carriage returns (\r) should not be colorized. It would mess
+ ;; up color handling in Emacs compilation buffers using
+ ;; `ansi-color-apply-on-region' in `compilation-filter-hook'.
+ (buttercup--print "\r%s"
+ (buttercup-colorize
+ (buttercup--indented-description spec) color)))
+ (unless (eq 'passed status)
+ (buttercup--print "%s"
+ (buttercup-colorize (concat " " failure) color)))
+ (buttercup--print " (%s)\n" (buttercup-elapsed-time-string spec))))
+
+(cl-defun buttercup-reporter-batch--print-failed-spec-report (failed-spec color)
+ "Print a failure report for FAILED-SPEC.
+
+Colorize parts of the output if COLOR is non-nil."
+ (when (eq buttercup-stack-frame-style 'omit)
+ (cl-return-from buttercup-reporter-batch--print-failed-spec-report))
+ (let ((description (buttercup-spec-failure-description failed-spec))
+ (stack (buttercup-spec-failure-stack failed-spec))
+ (full-name (buttercup-spec-full-name failed-spec)))
+ (if color
+ (setq full-name (buttercup-colorize full-name 'red)))
+ (buttercup--print "%s\n" (make-string 40 ?=))
+ (buttercup--print "%s\n" full-name)
+ (when stack
+ (buttercup--print "\nTraceback (most recent call last):\n")
+ (dolist (frame stack)
+ (let ((frame-text (buttercup--format-stack-frame frame)))
+ (buttercup--print "%s\n" frame-text))))
+ (cond
+ ((stringp description)
+ (buttercup--print "%s: %s\n"
+ (if color
+ (buttercup-colorize "FAILED" 'red)
+ "FAILED")
+ description))
+ ((and (consp description) (eq (car description) 'error))
+ (buttercup--print "%S: %S\n"
+ (car description)
+ (cadr description)))
+ (t
+ (buttercup--print "FAILED: %S\n" description)))
+ (buttercup--print "\n")))
+
+(defun buttercup-reporter-batch--print-summary (suites color)
+ "Print a summary of the reults of SUITES.
+
+Colorize parts of the output if COLOR is non-nil."
+ (let* ((defined (buttercup-suites-total-specs-defined suites))
+ (pending (buttercup-suites-total-specs-pending suites))
+ (failed (buttercup-suites-total-specs-failed suites))
+ (duration (seconds-to-string
+ (float-time
+ (time-subtract (current-time)
+ buttercup-reporter-batch--start-time))))
+ (out-of (if (zerop pending) "" (format " out of %d" defined)))
+ (failed-str (format "%d failed" failed)))
+ (if color
+ (setq failed-str (buttercup-colorize failed-str (if (zerop failed) 'green 'red))))
+ (buttercup--print
+ "Ran %d%s specs, %s, in %s.\n"
+ (- defined pending) out-of failed-str duration)))
(defun buttercup--print (fmt &rest args)
"Format a string and send it to terminal without alteration.
@@ -1795,9 +1875,12 @@ the capturing behavior."
,@body))
(defun buttercup-colorize (string color)
- "Format STRING with COLOR."
- (let ((color-code (cdr (assoc color buttercup-colors))))
- (format "\e[%sm%s\e[0m" color-code string)))
+ "Format STRING with COLOR.
+Return STRING unmodified if COLOR is nil."
+ (if color
+ (let ((color-code (cdr (assoc color buttercup-colors))))
+ (format "\e[%sm%s\e[0m" color-code string))
+ string))
(defun buttercup-reporter-interactive (event arg)
"Reporter for interactive sessions.
@@ -1847,34 +1930,62 @@ failed -- The second value is the description of the expectation
nil))))
(defun buttercup--debugger (&rest args)
+ "Debugger function that return error context with an exception.
+
+ARGS according to `debugger'."
;; If we do not do this, Emacs will not run this handler on
;; subsequent calls. Thanks to ert for this.
(setq num-nonmacro-input-events (1+ num-nonmacro-input-events))
(throw 'buttercup-debugger-continue
(list 'failed args (buttercup--backtrace))))
+(defalias 'buttercup--mark-stackframe 'ignore
+ "Marker to find where the backtrace start.")
+
(defun buttercup--backtrace ()
- (let* ((n 0)
- (frame (backtrace-frame n))
- (frame-list nil)
- (in-program-stack nil))
- (while frame
+ "Create a backtrace, a list of frames returned from `backtrace-frame'."
+ ;; Read the backtrace frames from 0 (the closest) upward.
+ (cl-do* ((n 0 (1+ n))
+ (frame (backtrace-frame n) (backtrace-frame n))
+ (frame-list nil)
+ (in-program-stack nil))
+ ((not frame) frame-list)
+ ;; discard frames until (and including) `buttercup--debugger', they
+ ;; only contain buttercup code
(when in-program-stack
(push frame frame-list))
(when (eq (elt frame 1)
'buttercup--debugger)
(setq in-program-stack t))
- (when (eq (elt frame 1)
- 'buttercup--funcall)
- (setq in-program-stack nil
- frame-list (nthcdr 6 frame-list)))
- (setq n (1+ n)
- frame (backtrace-frame n)))
- frame-list))
+ ;; keep frames until one of the known functions are found, after
+ ;; this is just the buttercup framework and not interesting for
+ ;; users incorrect for testing buttercup. Some frames before the
+ ;; function also have to be discarded
+ (cl-labels ((tree-find (key tree)
+ (cl-block tree-find
+ (while (consp tree)
+ (let ((elem (pop tree)))
+ (when (or (and (consp elem)
+ (tree-find key elem))
+ (eql key elem))
+ (cl-return-from tree-find t))))
+ (cl-return-from tree-find
+ (and tree (eql tree key))))))
+ (when (and in-program-stack (tree-find 'buttercup--mark-stackframe frame))
+ (pop frame-list)
+ (cl-return frame-list)))))
(defun buttercup--format-stack-frame (frame &optional style)
- (pcase (or style buttercup-stack-frame-style 'crop)
- (`full (format " %S" (cdr frame)))
+ "Format stack FRAME according to STYLE.
+STYLE can be one of `full', `crop', `pretty', or `omit'.
+If STYLE is nil, use `buttercup-stack-frame-style' or `crop'."
+ (setq style (or style buttercup-stack-frame-style 'crop))
+ (pcase style
+ (`omit) ; needed to verify valid styles
+ (`full
+ (if (car frame)
+ (format " %S%s" (cadr frame) (if (cddr frame) (prin1-to-string (cddr frame)) "()"))
+ (format " %S" (cdr frame))))
(`crop
(let ((line (buttercup--format-stack-frame frame 'full)))
;; Note: this could be done sith `s-truncate' from the s
@@ -1941,6 +2052,32 @@ With buttercup minor mode active the following is activated:
(cl-dolist (form imenu-forms)
(setq imenu-generic-expression (delete form imenu-generic-expression))))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Signal errors when files have to be recompiled
+
+(defun buttercup-check-for-stale-elc (elc-file)
+ "Raise an error when ELC-FILE is an elc-file and older than its el-file."
+ (when (string= (file-name-extension elc-file) "elc")
+ (let ((el-file (substring elc-file 0 -1)))
+ (when (and (file-exists-p el-file)
+ (time-less-p
+ (file-attribute-modification-time (file-attributes elc-file))
+ (file-attribute-modification-time (file-attributes el-file))))
+ (error "%s is newer than %s" el-file elc-file)))))
+
+(defun buttercup-error-on-stale-elc (&optional arg)
+ "Activate errors when an stale (older than .el) .elc-file is loaded.
+
+Enable the functionality if ARG is omitted or nil, toggle it if
+ARG is ‘toggle’; disable otherwise."
+ (cond ((null arg)
+ (add-hook 'after-load-functions #'buttercup-check-for-stale-elc))
+ ((eq arg 'toggle)
+ (if (memq 'buttercup-check-for-stale-elc after-load-functions)
+ (remove-hook 'after-load-functions #'buttercup-check-for-stale-elc)
+ (add-hook 'after-load-functions #'buttercup-check-for-stale-elc)))
+ (t (remove-hook 'after-load-functions #'buttercup-check-for-stale-elc))))
+
;; Local Variables:
;; indent-tabs-mode: nil
;; sentence-end-double-space: nil
diff --git a/docs/running-tests.md b/docs/running-tests.md
index 8bebdec..4ce580d 100644
--- a/docs/running-tests.md
+++ b/docs/running-tests.md
@@ -16,7 +16,7 @@ only those, and test your project in a well-defined environment.
Buttercup works best in such environments, so the following best
practices rely on Cask to be installed.
-## Project Directory Layout
+### Project Directory Layout
A basic project layout requires a project file, called `feature.el`
here, a `Cask` file to define dependencies, and a `tests/` directory
@@ -30,7 +30,7 @@ feature/feature.el
**feature.el**
-```Lisp
+```Emacs-Lisp
(defun featurize (bug feature)
(format "It's not a %s, it's a %s" bug feature))
@@ -59,7 +59,7 @@ feature/feature.el
"It's not a bug, it's a feature")))
```
-## Running Tests
+### Running Tests
You can now use Cask to run your tests.
@@ -100,11 +100,71 @@ named `test-*.el`, `*-test.el` or `*-tests.el`.
Use the `--pattern PATTERN` option to only Only run tests with names
matching PATTERN. The `--pattern` option can be used multiple times,
in which case tests will be run if they match any of the given
-patterns.
+patterns. Combine with the `--no-skip` option to filter out the
+skipped tests.
You can run this command whichever way you like. Common choices
include a makefile or shell scripts.
+## Eldev
+
+[Eldev](https://github.com/doublep/eldev) is another Elisp build tool
+and has built-in supports for Buttercup. Like Cask, it installs
+dependencies of your project automatically, ensuring consistent
+environment.
+
+### Project Directory Layout
+
+Typical project layout used for Cask also works for Eldev. However,
+the tool is very liberal and with a couple of lines you can configure
+it to handle almost any layout a project could have (see [its
+documentation](https://github.com/doublep/eldev#testing) if needed).
+
+Unlike Cask, however, Eldev needs that your test files include form
+`(require 'buttercup)`. Otherwise you will get Elisp errors like
+“Symbol’s function definition is void: describe”. Basically, Eldev
+requires that the `.el` files are *self-contained* and do not depend
+on certain external tool.
+
+### Running Tests
+
+The tool installs project dependencies automatically when needed, so
+you don’t have to bother about that. All you need to do is to ensure
+the main file of the project correctly declares them.
+
+You also don’t need to declare that the project uses Buttercup for
+testing: Eldev will determine that on-the-fly.
+
+To run your test, just execute:
+
+````
+$ eldev test
+[1/1] Installing package ‘buttercup’ (1.23) from ‘melpa-stable’...
+Running 1 specs.
+
+The feature
+ can use bug and feature (2.58ms)
+
+Ran 1 specs, 0 failed, in 2.68ms.
+````
+
+You can also specify patterns on the command line, to avoid running
+all the tests at once, i.e.:
+
+````
+$ eldev test foo
+````
+
+If you have several test files, you can avoid running all the tests by
+specifying filename after `-f` (`--file`) option:
+
+````
+$ eldev test -f main.el
+````
+
+For more information, please see tool’s own documentation, especially
+[the section about testing](https://github.com/doublep/eldev#testing).
+
## Projectile
If you use [Projectile](https://github.com/bbatsov/projectile) for interacting with your projects you can set the "default" project test command to be available when you invoke `projectile-test-project`. Create a `.dir-locals.el` file in the the root of your project tree (next to your Cask file). An example:
@@ -119,6 +179,9 @@ If you use [Projectile](https://github.com/bbatsov/projectile) for interacting w
projectile-test-cmd-map))))))
```
+If you are using Eldev as build tool, Projectile should provide
+testing command on its own, so you don’t need any special steps.
+
## Travis
If your project is hosted on github, you can use
@@ -147,3 +210,23 @@ script:
Most of the complexity here is from installing
[EVM](https://github.com/rejeep/evm) and Cask to be able to test your
project using different Emacs versions.
+
+For Eldev, use the following `.travis.yml` file:
+
+````
+language: emacs-lisp
+dist: trusty
+
+env:
+ # Add more lines like this if you want to test on different Emacs versions.
+ - EVM_EMACS=emacs-26.3-travis
+
+install:
+ - curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/travis-eldev-and-evm > x.sh && source ./x.sh
+ - evm install $EVM_EMACS --use
+
+script:
+ - eldev -p -dtT test
+````
+
+For details, see [tool’s own documentation](https://github.com/doublep/eldev#continuous-integration).
diff --git a/tests/test-buttercup.el b/tests/test-buttercup.el
index cb472d3..bda3e6f 100644
--- a/tests/test-buttercup.el
+++ b/tests/test-buttercup.el
@@ -28,6 +28,7 @@
(require 'ansi-color)
(require 'ert)
(require 'cl-lib)
+(require 'imenu)
(defun make-list-of-closures (items)
"For each element of ITEMS, return a closure returning it."
@@ -36,18 +37,65 @@
items))
(defmacro with-local-buttercup (&rest body)
- "Execute BODY with local buttercup state variables."
+ "Execute BODY with local buttercup state variables.
+Keyword arguments kan be used to override the values of certain
+variables:
+ :color -> `buttercup-color'
+ :frame-style -> `buttercup-stack-frame-style'
+ :reporter -> `buttercup-reporter'
+ :suites -> `buttercup-suites'
+ :quiet -> `buttercup-reporter-batch-quiet-statuses'
+\n(fn &keys COLOR SUITES REPORTER &rest BODY)"
(declare (debug t) (indent defun))
- `(let (buttercup--after-all
- buttercup--after-each
- buttercup--before-all
+ ;; extract keyword arguments
+ (let ((keys '(:color buttercup-color
+ :frame-style buttercup-stack-frame-style
+ :reporter buttercup-reporter
+ :suites buttercup-suites
+ :quiet buttercup-reporter-batch-quiet-statuses))
+ extra-vars)
+ (while (plist-member keys (car body))
+ (push (list (plist-get keys (pop body)) (pop body)) extra-vars))
+ `(let (buttercup--after-each
buttercup--before-each
(buttercup--cleanup-functions :invalid)
buttercup--current-suite
(buttercup-reporter #'ignore)
buttercup-suites
- (buttercup-warning-buffer-name " *ignored buttercup warnings*"))
- ,@body))
+ buttercup-color
+ buttercup-reporter-batch-quiet-statuses
+ buttercup-reporter-batch--suite-stack
+ buttercup-reporter-batch--failures
+ (buttercup-stack-frame-style 'crop)
+ (buttercup-warning-buffer-name " *ignored buttercup warnings*")
+ ,@(nreverse extra-vars))
+ ,@body)))
+
+(defmacro buttercup--test-with-tempdir (files &rest body)
+ "Create FILES and execute BODY in a temporary directory.
+FILES shall be a list of file names. An empty file with that name
+will be created in the temporary directory. Any path prefix for a
+file will be created in the temporary directory."
+ (declare (debug t) (indent defun))
+ (let ((tmproot (cl-gensym))
+ (subdir (cl-gensym))
+ (olddir (cl-gensym))
+ (file (cl-gensym)))
+ `(let ((,tmproot (make-temp-file "buttercup-test-temp-" t))
+ ,subdir
+ (,olddir default-directory))
+ (dolist (,file ,files)
+ (setq ,subdir (concat ,tmproot "/" (file-name-directory ,file)))
+ (when (and ,subdir (not (file-exists-p ,subdir)))
+ (make-directory ,subdir t))
+ (write-region "" nil (concat ,tmproot "/" ,file)))
+ ;; It is tempting to use unwind-protect or condition-case here,
+ ;; but that will mask actual test failures by interfering with
+ ;; the debugger installed by buttercup
+ (cd ,tmproot)
+ ,@body
+ (cd ,olddir)
+ (delete-directory ,tmproot t))))
(defun send-string-to-ansi-buffer (buffer string)
"A `send-string-to-terminal' variant that sends STRING to BUFFER.
@@ -114,8 +162,10 @@ text properties using `ansi-color-apply'."
(expect (length expansion) :to-equal 4)
(expect (nth 0 expansion) :to-be 'buttercup-expect)
(expect (functionp (nth 1 expansion)))
+ (expect (buttercup--wrapper-fun-p (nth 1 expansion)))
(expect (nth 2 expansion) :to-be :to-equal)
- (expect (functionp (nth 3 expansion)))))
+ (expect (functionp (nth 3 expansion)))
+ (expect (buttercup--wrapper-fun-p (nth 3 expansion)))))
(it "with no matcher should use `:to-be-truthy' as the matcher"
(let ((expansion (macroexpand '(expect (equal (+ 1 1) 2)))))
@@ -280,7 +330,7 @@ text properties using `ansi-color-apply'."
(buttercup-suite-add-child grandparent parent)
(buttercup-suite-add-child parent child)
- (expect (buttercup-suite-parents child)
+ (expect (buttercup-suite-or-spec-parents child)
:to-equal
(list parent grandparent)))))
@@ -292,7 +342,7 @@ text properties using `ansi-color-apply'."
(buttercup-suite-add-child grandparent parent)
(buttercup-suite-add-child parent child)
- (expect (buttercup-spec-parents child)
+ (expect (buttercup-suite-or-spec-parents child)
:to-equal
(list parent grandparent)))))
@@ -327,8 +377,8 @@ text properties using `ansi-color-apply'."
(expect (buttercup-suites-total-specs-pending suites)
:to-equal 2)))
(it "should also count skipped specs"
- (with-local-buttercup
- (buttercup--mark-skipped suites (list "skipped"))
+ (with-local-buttercup :suites suites
+ (buttercup-mark-skipped "skipped" t)
(expect (buttercup-suites-total-specs-pending suites)
:to-equal 3))))
@@ -538,6 +588,7 @@ text properties using `ansi-color-apply'."
'(buttercup-it "description"
(lambda ()
(buttercup-with-converted-ert-signals
+ (buttercup--mark-stackframe)
body)))))
(it "without argument should expand to xit."
@@ -816,7 +867,7 @@ text properties using `ansi-color-apply'."
(let ((suite (describe "A bad spy scope"
(before-all
(spy-on 'some-function)))))
- (expect (run--suite suite)
+ (expect (buttercup--run-suite suite)
:to-throw))))
(it "used directly in describe"
(with-local-buttercup
@@ -1100,43 +1151,47 @@ text properties using `ansi-color-apply'."
(setq skipped (make-buttercup-spec :description "skipped" :status 'pending)))
(it "should print the number of specs"
- (let ((buttercup-reporter-batch--failures nil))
+ (with-local-buttercup :color nil
(buttercup-reporter-batch 'buttercup-started (list parent-suite)))
(expect (buttercup-output) :to-equal-including-properties "Running 1 specs.\n\n"))
(it "should color-print the number of specs with the default color"
- (let (buttercup-reporter-batch--failures)
- (buttercup-reporter-batch-color 'buttercup-started (list parent-suite)))
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'buttercup-started (list parent-suite)))
(expect (buttercup-output) :to-equal-including-properties "Running 1 specs.\n\n"))
(it "should print the number of skipped specs"
- (let ((buttercup-reporter-batch--failures nil))
+ (with-local-buttercup :color nil
(buttercup-suite-add-child child-suite skipped)
(buttercup-reporter-batch 'buttercup-started (list parent-suite)))
(expect (buttercup-output) :to-equal-including-properties "Running 1 out of 2 specs.\n\n"))
(it "should color-print the number of skipped specs with the default color"
- (let (buttercup-reporter-batch--failures)
+ (with-local-buttercup :color t
(buttercup-suite-add-child child-suite skipped)
- (buttercup-reporter-batch-color 'buttercup-started (list parent-suite)))
+ (buttercup-reporter-batch 'buttercup-started (list parent-suite)))
(expect (buttercup-output) :to-equal-including-properties "Running 1 out of 2 specs.\n\n")))
(describe "on the suite-started event"
(it "should emit an indented suite description"
- (buttercup-reporter-batch 'suite-started child-suite)
+ (with-local-buttercup :color nil
+ (buttercup-reporter-batch 'suite-started child-suite))
(expect (buttercup-output) :to-equal-including-properties " child-suite\n"))
(it "should color-print an indented suite description with the default color"
- (buttercup-reporter-batch-color 'suite-started child-suite)
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'suite-started child-suite))
(expect (buttercup-output) :to-equal-including-properties " child-suite\n")))
(describe "on the spec-started event"
(it "should emit an indented spec description"
- (buttercup-reporter-batch 'spec-started spec)
+ (with-local-buttercup :color nil
+ (buttercup-reporter-batch 'spec-started spec))
(expect (buttercup-output) :to-equal-including-properties " spec"))
(it "should color-print an indented spec description with the default color"
- (buttercup-reporter-batch-color 'spec-started spec)
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'spec-started spec))
(expect (buttercup-output) :to-equal-including-properties " spec")))
(describe "on the spec-done event"
@@ -1147,15 +1202,17 @@ text properties using `ansi-color-apply'."
(buttercup--set-end-time spec))
(it "should print no status tag"
- (buttercup-reporter-batch 'spec-started spec)
- (buttercup-reporter-batch 'spec-done spec)
+ (with-local-buttercup :color nil
+ (buttercup-reporter-batch 'spec-started spec)
+ (buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
(format " spec (%s)\n"
(buttercup-elapsed-time-string spec))))
(it "should color-print the description in green and no status tag"
- (buttercup-reporter-batch-color 'spec-started spec)
- (buttercup-reporter-batch-color 'spec-done spec)
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'spec-started spec)
+ (buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
(ansi-color-apply
(format "\e[32m spec\e[0m (%s)\n"
@@ -1163,16 +1220,18 @@ text properties using `ansi-color-apply'."
(it "should print multiline specs cleanly"
(setf (buttercup-spec-description spec) "one\ntwo\vthree")
- (buttercup-reporter-batch 'spec-started spec)
- (buttercup-reporter-batch 'spec-done spec)
+ (with-local-buttercup :color nil
+ (buttercup-reporter-batch 'spec-started spec)
+ (buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
(format " one\ntwo\n three (%s)\n"
(buttercup-elapsed-time-string spec))))
(it "should color-print multiline specs cleanly"
(setf (buttercup-spec-description spec) "one\ntwo\vthree")
- (buttercup-reporter-batch-color 'spec-started spec)
- (buttercup-reporter-batch-color 'spec-done spec)
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'spec-started spec)
+ (buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
(ansi-color-apply
(format "\e[32m one\ntwo\n three\e[0m (%s)\n"
@@ -1185,7 +1244,7 @@ text properties using `ansi-color-apply'."
(buttercup--set-end-time spec))
(it "should say FAILED"
- (let ((buttercup-reporter-batch--failures nil))
+ (with-local-buttercup :color nil
(buttercup-reporter-batch 'spec-started spec)
(buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
@@ -1193,9 +1252,9 @@ text properties using `ansi-color-apply'."
(buttercup-elapsed-time-string spec))))
(it "should color-print the description in red and say FAILED"
- (let ((buttercup-reporter-batch--failures nil))
- (buttercup-reporter-batch-color 'spec-started spec)
- (buttercup-reporter-batch-color 'spec-done spec))
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'spec-started spec)
+ (buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
(ansi-color-apply
(format "\e[31m spec FAILED\e[0m (%s)\n"
@@ -1209,7 +1268,7 @@ text properties using `ansi-color-apply'."
(buttercup--set-end-time spec))
(it "should output the failure-description"
- (let ((buttercup-reporter-batch--failures nil))
+ (with-local-buttercup :color nil
(buttercup-reporter-batch 'spec-started spec)
(buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
@@ -1217,34 +1276,44 @@ text properties using `ansi-color-apply'."
(buttercup-elapsed-time-string spec))))
(it "should color-print the description and failure-description in yellow"
- (let ((buttercup-reporter-batch--failures nil))
- (buttercup-reporter-batch-color 'spec-started spec)
- (buttercup-reporter-batch-color 'spec-done spec))
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'spec-started spec)
+ (buttercup-reporter-batch 'spec-done spec))
(expect (buttercup-output) :to-equal-including-properties
(ansi-color-apply
(format "\e[33m spec DESCRIPTION\e[0m (%s)\n"
(buttercup-elapsed-time-string spec))))))
- (it "should throw an error for an unknown spec status"
- (setf (buttercup-spec-status spec) 'unknown)
- (expect (buttercup-reporter-batch 'spec-done spec)
- :to-throw)))
+ (describe "should throw an error for an unknown spec status"
+ (before-each (setf (buttercup-spec-status spec) 'unknown))
+ (it "for plain output"
+ (with-local-buttercup :color nil
+ (expect (buttercup-reporter-batch 'spec-done spec)
+ :to-throw)))
+ (it "for colored output"
+ (with-local-buttercup :color t
+ (expect (buttercup-reporter-batch 'spec-done spec)
+ :to-throw)))))
(describe "on the suite-done event"
- (it "should emit a newline at the end of the top-level suite"
- (buttercup-reporter-batch 'suite-done parent-suite)
+ (it "should emit a newline at the end of a top-level suite"
+ (with-local-buttercup :color nil
+ (buttercup-reporter-batch 'suite-done parent-suite))
(expect (buttercup-output) :to-equal-including-properties "\n"))
- (it "should color-print a newline at the end of the top-level suite"
- (buttercup-reporter-batch-color 'suite-done parent-suite)
+ (it "should color-print a newline at the end of a top-level suite"
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'suite-done parent-suite))
(expect (buttercup-output) :to-equal-including-properties "\n"))
(it "should not emit anything at the end of other suites"
- (buttercup-reporter-batch 'suite-done child-suite)
+ (with-local-buttercup :color nil
+ (buttercup-reporter-batch 'suite-done child-suite))
(expect (buttercup-output) :to-equal-including-properties ""))
(it "should not color-print anything at the end of other suites"
- (buttercup-reporter-batch-color 'suite-done child-suite)
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'suite-done child-suite))
(expect (buttercup-output) :to-equal-including-properties "")))
(describe "on the buttercup-done event"
@@ -1253,60 +1322,59 @@ text properties using `ansi-color-apply'."
(before-each
(setq defined-specs 10 pending-specs 0 failed-specs 0)
- (spy-on 'buttercup-suites-total-specs-defined :and-call-fake (lambda (&rest a) defined-specs))
- (spy-on 'buttercup-suites-total-specs-pending :and-call-fake (lambda (&rest a) pending-specs))
- (spy-on 'buttercup-suites-total-specs-failed :and-call-fake (lambda (&rest a) failed-specs)))
+ (spy-on 'buttercup-suites-total-specs-defined :and-call-fake (lambda (&rest _) defined-specs))
+ (spy-on 'buttercup-suites-total-specs-pending :and-call-fake (lambda (&rest _) pending-specs))
+ (spy-on 'buttercup-suites-total-specs-failed :and-call-fake (lambda (&rest _) failed-specs)))
(it "should print a summary of run and failing specs"
(setq failed-specs 6)
- (let (buttercup-reporter-batch--failures)
+ (with-local-buttercup :color nil
(buttercup-reporter-batch 'buttercup-done nil))
(expect (buttercup-output) :to-match
"Ran 10 specs, 6 failed, in [0-9]+.[0-9]+[mu]?s.\n"))
(it "should color-print `0 failed' specs in green"
- (let (buttercup-reporter-batch--failures)
- (buttercup-reporter-batch-color 'buttercup-done nil))
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'buttercup-done nil))
(expect (buttercup-output) :to-match
"Ran 10 specs, 0 failed, in [0-9]+.[0-9]+[mu]?s.\n")
(expect (substring (buttercup-output) 0 (length "Ran 10 specs, 0 failed, in"))
:to-equal-including-properties
- (ansi-color-apply "Ran 10 specs,\e[32m 0 failed\e[0m, in")))
+ (ansi-color-apply "Ran 10 specs, \e[32m0 failed\e[0m, in")))
(it "should color-print `X failed' specs in red"
(setq failed-specs 6)
- (let (buttercup-reporter-batch--failures)
- (buttercup-reporter-batch-color 'buttercup-done nil))
+ (with-local-buttercup :color t
+ (buttercup-reporter-batch 'buttercup-done nil))
(expect (buttercup-output) :to-match
"Ran 10 specs, 6 failed, in [0-9]+.[0-9]+[mu]?s.\n")
(expect (substring (buttercup-output) 0 (length "Ran 10 specs, 6 failed, in"))
:to-equal-including-properties
- (ansi-color-apply "Ran 10 specs,\e[31m 6 failed\e[0m, in")))
+ (ansi-color-apply "Ran 10 specs, \e[31m6 failed\e[0m, in")))
(it "should print a summary separating run and pending specs"
(setq pending-specs 3)
- (let (buttercup-reporter-batch--failures)
+ (with-local-buttercup :color nil
(buttercup-reporter-batch 'buttercup-done nil))
(expect (buttercup-output) :to-match
"Ran 7 out of 10 specs, 0 failed, in [0-9]+.[0-9]+[mu]?s.\n"))
(it "should color-print pending spec count in default color"
(setq pending-specs 3)
- (let (buttercup-reporter-batch--failures)
+ (with-local-buttercup :color t
(buttercup-reporter-batch 'buttercup-done nil))
(expect (buttercup-output) :to-match
"Ran 7 out of 10 specs, 0 failed, in [0-9]+.[0-9]+[mu]?s.\n")
(expect (substring (buttercup-output)
- 0 (length "Ran 7 out of 10 specs, 0 failed, in"))
+ 0 (length "Ran 7 out of 10 specs"))
:to-equal-including-properties
- "Ran 7 out of 10 specs, 0 failed, in"))
+ "Ran 7 out of 10 specs"))
(it "should not raise any error even if a spec failed"
(setf (buttercup-spec-status spec) 'failed)
- (let (buttercup-reporter-batch--failures)
+ (with-local-buttercup :color nil
(expect (buttercup-reporter-batch 'buttercup-done (list spec))
:not :to-throw)))
- ;; TODO: Backtrace tests
)
(describe "on an unknown event"
@@ -1314,42 +1382,358 @@ text properties using `ansi-color-apply'."
(expect (buttercup-reporter-batch 'unknown-event nil)
:to-throw)))))
+(describe "Backtraces"
+ :var (print-buffer)
+ ;; redirect output to a buffer
+ (before-each
+ (setq print-buffer (generate-new-buffer "*btrcp-reporter-test*"))
+ (spy-on 'send-string-to-terminal :and-call-fake
+ (apply-partially #'send-string-to-ansi-buffer print-buffer))
+ ;; Convenience function
+ (spy-on 'buttercup-output :and-call-fake
+ (lambda ()
+ "Return the text of print-buffer."
+ (with-current-buffer print-buffer
+ (buffer-string)))))
+ (after-each
+ (kill-buffer print-buffer)
+ (setq print-buffer nil))
+ ;; define a buttercup-reporter-batch variant that only outputs on
+ ;; buttercup-done
+ (before-each
+ (spy-on 'backtrace-reporter :and-call-fake
+ (lambda (event arg)
+ (if (eq event 'buttercup-done)
+ (buttercup-reporter-batch event arg)
+ (cl-letf (((symbol-function 'buttercup--print) #'ignore))
+ (buttercup-reporter-batch event arg))))))
+ ;; suppress the summary line
+ (before-each
+ (spy-on 'buttercup-reporter-batch--print-summary))
+ ;; define a known backtrace with a typical error
+ (before-all
+ (defun bc-bt-foo (a) (bc-bt-bar a))
+ (defun bc-bt-bar (a) (bc-bt-baz a))
+ (defun bc-bt-baz (a)
+ (or (number-or-marker-p a)
+ (signal 'wrong-type-argument `(number-or-marker-p ,a)))))
+ (after-all
+ (fmakunbound 'bc-bt-foo)
+ (fmakunbound 'bc-bt-bar)
+ (fmakunbound 'bc-bt-baz))
+ (it "should be printed for each failed spec"
+ (with-local-buttercup
+ :reporter #'backtrace-reporter
+ (describe "suite"
+ (it "expect 2" (expect (+ 1 2) :to-equal 2))
+ (it "expect nil" (expect nil)))
+ (buttercup-run :noerror))
+ (expect (buttercup-output) :to-match
+ (rx string-start
+ (= 2 (seq (= 40 ?=) "\n"
+ "suite expect " (or "2" "nil") "\n"
+ "\n"
+ "Traceback (most recent call last):\n"
+ (* (seq " " (+ not-newline) "\n"))
+ (or "FAILED" "error") ": " (+ not-newline) "\n\n"))
+ string-end)))
+ (describe "with style"
+ :var (test-suites long-string)
+ ;; Set up tests to test
+ (before-each
+ (setq long-string
+ ;; It's important that this string doesn't contain any
+ ;; regex special characters, it's used in a `rx' `eval'
+ ;; form that will escape them. Later Emacsen have
+ ;; `literal' that is much easier to use.
+ "a string that will be truncated in backtrace crop, at least 70 chars long")
+ (with-local-buttercup
+ (describe "suite"
+ (it "bc-bt-backtrace"
+ (expect
+ (bc-bt-foo long-string)
+ :to-be-truthy)))
+ (setq test-suites buttercup-suites)))
+ (it "`crop' should print truncated lines"
+ (with-local-buttercup
+ :suites test-suites :reporter #'backtrace-reporter
+ :frame-style 'crop
+ (buttercup-run :noerror)
+ (setq long-string (truncate-string-to-width long-string 63))
+ (expect (buttercup-output) :to-match
+ (rx-to-string
+ `(seq
+ string-start
+ (= 40 ?=) "\n"
+ "suite bc-bt-backtrace\n"
+ "\n"
+ "Traceback (most recent call last):\n"
+ " bc-bt-foo(\"" (eval ,long-string) "...\n"
+ " bc-bt-bar(\"" (eval ,long-string) "...\n"
+ " bc-bt-baz(\"" (eval ,long-string) "...\n"
+ (* (seq " " (or (seq (= 74 not-newline) (= 3 ?.))
+ (seq (** 0 74 not-newline) (= 3 (not (any ?.))))) "\n"))
+ "error: (" (* anything) ")\n\n"
+ string-end)))))
+ (it "`full' should print full lines"
+ (with-local-buttercup
+ :suites test-suites :reporter #'backtrace-reporter
+ :frame-style 'full
+ (buttercup-run :noerror)
+ (expect (buttercup-output) :to-match
+ (rx-to-string
+ `(seq
+ string-start
+ (= 40 ?=) "\n"
+ "suite bc-bt-backtrace\n"
+ "\n"
+ "Traceback (most recent call last):\n"
+ " bc-bt-foo(\"" (eval ,long-string) "\")\n"
+ " bc-bt-bar(\"" (eval ,long-string) "\")\n"
+ " bc-bt-baz(\"" (eval ,long-string) "\")\n"
+ (* (seq " " (* not-newline) (= 3 (not (any ?.))) "\n"))
+ "error: (" (* anything) ")\n\n"
+ string-end)))))
+ (it "`pretty' should pretty-print frames"
+ (with-local-buttercup
+ :suites test-suites :reporter #'backtrace-reporter
+ :frame-style 'pretty
+ (buttercup-run :noerror)
+ (expect (buttercup-output) :to-match
+ (rx-to-string
+ `(seq
+ string-start
+ (= 40 ?=) "\n"
+ "suite bc-bt-backtrace\n"
+ "\n"
+ "Traceback (most recent call last):\n"
+ "λ (bc-bt-foo \"" (regex ,long-string) "\")\n"
+ "λ (bc-bt-bar \"" (regex ,long-string) "\")\n"
+ "λ (bc-bt-baz \"" (regex ,long-string) "\")\n"
+ (* (seq (or ?M ?λ) " (" (* not-newline) ; frame start
+ (*? (seq "\n " (* not-newline))) ; any number of pp lines
+ (* not-newline) ")\n")) ;; frame end
+ "error: (" (* anything) ")\n\n"
+ string-end)))))
+ (it "`omit' should print nothing"
+ (with-local-buttercup
+ :suites test-suites :reporter #'backtrace-reporter
+ :frame-style 'omit
+ (buttercup-run :noerror)
+ (expect (buttercup-output) :to-equal ""))))
+ (it "should signal an error for unknown styles"
+ (let ((buttercup-stack-frame-style 'not-a-valid-style))
+ (expect (buttercup--format-stack-frame '(t myfun 1 2))
+ :to-throw 'error '("Unknown stack trace style: not-a-valid-style"))))
+ (describe "should generate correct backtrace for"
+ (cl-macrolet
+ ((matcher-spec
+ (description &rest matcher)
+ `(it ,description
+ (with-local-buttercup
+ :reporter #'backtrace-reporter
+ (describe "backtrace for"
+ (it "matcher"
+ (expect (bc-bt-baz "text") ,@matcher)))
+ (buttercup-run :noerror)
+ (expect (buttercup-output) :to-equal
+ ,(mapconcat
+ #'identity
+ `(,(make-string 40 ?=)
+ "backtrace for matcher"
+ ""
+ "Traceback (most recent call last):"
+ " bc-bt-baz(\"text\")"
+ ,(concat
+ " (or (number-or-marker-p a) (signal "
+ (if (< emacs-major-version 27)
+ "(quote wrong-type-argument) (list (quot..."
+ "'wrong-type-argument (list 'number-or-m..."))
+ " signal(wrong-type-argument (number-or-marker-p \"text\"))"
+ "error: (wrong-type-argument number-or-marker-p \"text\")"
+ "" "") "\n"))))))
+ (matcher-spec "no matcher")
+ (matcher-spec ":to-be-truthy" :to-be-truthy)
+ (matcher-spec ":not :to-be-truthy" :not :to-be-truthy)
+ (matcher-spec ":to-be" :to-be 3)
+ (matcher-spec ":not :to-be" :not :to-be 3)
+ (matcher-spec ":to-equal" :to-equal 3)
+ (matcher-spec ":not :to-equal" :not :to-equal 3)
+ (matcher-spec ":to-have-same-items-as" :to-have-same-items-as '(3))
+ (matcher-spec ":not :to-have-same-items-as" :not :to-have-same-items-as '(3))
+ (matcher-spec ":to-match" :to-match ".")
+ (matcher-spec ":not :to-match" :not :to-match ".")
+ (matcher-spec ":to-be-in" :to-be-in '(2))
+ (matcher-spec ":not :to-be-in" :not :to-be-in '(2))
+ (matcher-spec ":to-contain" :to-contain 2)
+ (matcher-spec ":not :to-contain" :not :to-contain 2)
+ (matcher-spec ":to-be-less-than" :to-be-less-than 2)
+ (matcher-spec ":not :to-be-less-than" :not :to-be-less-than 2)
+ (matcher-spec ":to-be-greater-than" :to-be-greater-than 2)
+ (matcher-spec ":not :to-be-greater-than" :not :to-be-greater-than 2)
+ (matcher-spec ":to-be-weakly-less-than" :to-be-weakly-less-than 2)
+ (matcher-spec ":not :to-be-weakly-less-than" :not :to-be-weakly-less-than 2)
+ (matcher-spec ":to-be-weakly-greater-than" :to-be-weakly-greater-than 2)
+ (matcher-spec ":not :to-be-weakly-greater-than" :not :to-be-weakly-greater-than 2)
+ (matcher-spec ":to-be-close-to" :to-be-close-to 2 0.3)
+ (matcher-spec ":not :to-be-close-to" :not :to-be-close-to 2 0.2)
+ ;; (matcher-spec ":to-throw" :to-throw)
+ ;; (matcher-spec ":not :to-throw" :not :to-throw)
+ (matcher-spec ":to-have-been-called" :to-have-been-called)
+ (matcher-spec ":not :to-have-been-called" :not :to-have-been-called)
+ (matcher-spec ":to-have-been-called-with" :to-have-been-called-with 2)
+ (matcher-spec ":not :to-have-been-called-with" :not :to-have-been-called-with 2)
+ (matcher-spec ":to-have-been-called-times" :to-have-been-called-times 2)
+ (matcher-spec ":not :to-have-been-called-times" :not :to-have-been-called-times 2))))
+
+
+(describe "When using quiet specs in the batch reporter"
+ :var (print-buffer)
+ (before-each
+ (setq print-buffer (generate-new-buffer "*btrcp-reporter-test*"))
+ (spy-on 'send-string-to-terminal :and-call-fake
+ (apply-partially #'send-string-to-ansi-buffer print-buffer))
+ ;; Convenience function
+ (spy-on 'buttercup-output :and-call-fake
+ (lambda ()
+ "Return the text of `print-buffer'."
+ (with-current-buffer print-buffer
+ (buffer-string)))))
+ (after-each
+ (kill-buffer print-buffer)
+ (setq print-buffer nil))
+
+ (it "should print nothing if all specs are quiet"
+ (with-local-buttercup :color nil :quiet '(pending) :reporter #'buttercup-reporter-batch
+ (describe "top"
+ (it "spec 1")
+ (describe "second"
+ (it "spec 2")
+ (it "spec 3")))
+ (describe "empty")
+ (buttercup-run))
+ (expect (buttercup-output) :to-match
+ "^Running 0 out of 3 specs\\.\n\nRan 0 out of 3 specs, 0 failed, in [0-9.]+ms\\.$"))
+
+ (it "should print the containing suites for non-quiet specs"
+ (with-local-buttercup :color nil :quiet '(pending) :reporter #'buttercup-reporter-batch
+ (describe "top"
+ (it "spec 1" (ignore))
+ (describe "second"
+ (it "spec 2")
+ (it "spec 3" (ignore))
+ (describe "third"
+ (it "spec 4"))))
+ (describe "empty")
+ (buttercup-run))
+ (expect (buttercup-output) :to-match
+ (concat "^Running 2 out of 4 specs\\.\n\n"
+ "top\n"
+ " spec 1 ([0-9.]+ms)\n"
+ " second\n"
+ " spec 3 ([0-9.]+ms)\n\n"
+ "Ran 2 out of 4 specs, 0 failed, in [0-9.]+ms\\.$")))
+
+ (it "should quiet all of the given spec statuses"
+ ;; suppress stacktraces printed at buttercup-done
+ (spy-on 'buttercup-reporter-batch--print-failed-spec-report)
+ (with-local-buttercup
+ :color nil :quiet '(pending passed failed) :reporter #'buttercup-reporter-batch
+ (describe "passed"
+ (it "passed" (ignore)))
+ (describe "failed"
+ (it "failed" (buttercup-fail "because")))
+ (describe "pending"
+ (it "pending"))
+ (buttercup-run t))
+ (expect (buttercup-output) :to-match
+ "^Running 2 out of 3 specs\\.\n\nRan 2 out of 3 specs, 1 failed, in [0-9.]+ms\\.$"))
+
+ (it "should handle `skipped' virtual status in quiet list"
+ ;; suppress stacktraces printed at buttercup-done
+ (spy-on 'buttercup-reporter-batch--print-failed-spec-report)
+ (with-local-buttercup
+ :color nil :quiet '(skipped) :reporter #'buttercup-reporter-batch
+ (describe "passed"
+ (it "passed" (ignore)))
+ (describe "failed"
+ (it "failed" (buttercup-fail "because")))
+ (describe "pending"
+ (it "pending"))
+ (describe "skipped"
+ (it "skipped" (ignore)))
+ (buttercup-mark-skipped "skipped")
+ (buttercup-run t))
+ (expect (buttercup-output) :to-match
+ (concat "^Running 2 out of 4 specs\\.\n\n"
+ "passed\n passed ([0-9.]+ms)\n\n"
+ "failed\n failed because ([0-9.]+ms)\n\n"
+ "pending\n pending PENDING ([0-9.]+ms)\n\n"
+ "Ran 2 out of 4 specs, 1 failed, in [0-9.]+ms\\.\n$")))
+
+ (it "should handle `disabled' virtual status in quiet list"
+ ;; suppress stacktraces printed at buttercup-done
+ (spy-on 'buttercup-reporter-batch--print-failed-spec-report)
+ (with-local-buttercup
+ :color nil :quiet '(disabled) :reporter #'buttercup-reporter-batch
+ (describe "passed"
+ (it "passed" (ignore)))
+ (describe "failed"
+ (it "failed" (buttercup-fail "because")))
+ (describe "pending"
+ (it "pending"))
+ (describe "skipped"
+ (it "skipped" (ignore)))
+ (buttercup-mark-skipped "skipped")
+ (buttercup-run t))
+ (expect (buttercup-output) :to-match
+ (concat "^Running 2 out of 4 specs\\.\n\n"
+ "passed\n passed ([0-9.]+ms)\n\n"
+ "failed\n failed because ([0-9.]+ms)\n\n"
+ "skipped\n skipped SKIPPED ([0-9.]+ms)\n\n"
+ "Ran 2 out of 4 specs, 1 failed, in [0-9.]+ms\\.\n$"))))
+
+;;;;;;;;;;;;;;;;;;;;;
+;;; buttercup-run
+
(describe "The `buttercup-run' function"
- :var (parent-suite child-suite spec reporter)
+ :var (parent-suite child-suite spec)
(before-each
- (ignore reporter)
- (setf (symbol-function 'reporter) (lambda (event arg) (ignore event arg)))
(setq parent-suite (make-buttercup-suite :description "parent-suite")
child-suite (make-buttercup-suite :description "child-suite")
spec (make-buttercup-spec :description "spec"))
(buttercup-suite-add-child parent-suite child-suite)
(buttercup-suite-add-child child-suite spec)
- (spy-on 'reporter))
+ (spy-on 'reporter)
+ (spy-on 'buttercup--run-suite))
+ (it "should signal an error if no suites are defined"
+ (with-local-buttercup
+ (expect (buttercup-run) :to-throw 'error '("No suites defined"))))
+ (it "should return :no-suites for no suites and noerror"
+ (with-local-buttercup
+ (expect (buttercup-run t) :to-equal :no-suites)))
(it "should raise an error if at least one spec failed"
(setf (buttercup-spec-status spec) 'failed)
- (cl-letf (((symbol-function 'buttercup--run-suite) #'ignore)
- (buttercup-reporter 'reporter))
- (let ((buttercup-suites (list parent-suite)))
- (expect (buttercup-run) :to-throw))))
+ (with-local-buttercup :suites (list parent-suite)
+ (expect (buttercup-run) :to-throw 'error '(""))))
+ (it "should return nil for failing specs and noerror"
+ (setf (buttercup-spec-status spec) 'failed)
+ (with-local-buttercup :suites (list parent-suite)
+ (expect (buttercup-run t) :not :to-be-truthy)))
+ (it "should return t for passing specs"
+ (with-local-buttercup :suites (list parent-suite)
+ (expect (buttercup-run) :to-be-truthy)
+ (expect (buttercup-run t) :to-be-truthy)))
(it "should call the reporter twice with events buttercup-started and -done"
- (cl-letf (((symbol-function 'buttercup--run-suite) #'ignore)
- (buttercup-reporter 'reporter))
- (let ((buttercup-suites (list parent-suite)))
- (expect (buttercup-run) :not :to-throw)
- (expect 'reporter :to-have-been-called-times 2)
- (expect 'reporter :to-have-been-called-with 'buttercup-started buttercup-suites)
- (expect 'reporter :to-have-been-called-with 'buttercup-done buttercup-suites)))
- )
+ (with-local-buttercup :suites (list parent-suite) :reporter 'reporter
+ (expect (buttercup-run) :not :to-throw)
+ (expect 'reporter :to-have-been-called-times 2)
+ (expect 'reporter :to-have-been-called-with 'buttercup-started buttercup-suites)
+ (expect 'reporter :to-have-been-called-with 'buttercup-done buttercup-suites)))
(it "should call `buttercup--run-suite' once per suite"
- (let ((buttercup-suites (list parent-suite)) runner)
- (ignore runner)
- (setf (symbol-function 'runner) (lambda (suite) (ignore suite)))
- (spy-on 'runner)
- (cl-letf (((symbol-function 'buttercup--run-suite) #'runner)
- (buttercup-reporter 'reporter)
- (buttercup-suites (make-list 5 parent-suite)))
- (expect (buttercup-run) :not :to-throw)
- (expect 'runner :to-have-been-called-times 5)))))
+ (with-local-buttercup :reporter 'reporter :suites (make-list 5 parent-suite)
+ (expect (buttercup-run) :not :to-throw)
+ (expect 'buttercup--run-suite :to-have-been-called-times 5))))
(describe "The `buttercup--print' function"
(before-each
@@ -1362,7 +1746,7 @@ text properties using `ansi-color-apply'."
:to-have-been-called-with
"Hello, world")))
-(describe "The `buttercup--mark-skipped' function"
+(describe "The `buttercup-mark-skipped' function"
:var (suites)
(before-each
(with-local-buttercup
@@ -1381,44 +1765,185 @@ text properties using `ansi-color-apply'."
(it "2-3 spec" (ignore))
(it "2-4 spec" (ignore)))
(setq suites buttercup-suites)))
- (it "should do nothing with a match-all pattern"
+ (it "should do nothing with a reversed match-all pattern"
(expect (buttercup-suites-total-specs-defined suites) :to-equal 11)
(expect (buttercup-suites-total-specs-pending suites) :to-equal 5)
- (buttercup--mark-skipped suites '("."))
+ (with-local-buttercup :suites suites
+ (buttercup-mark-skipped "." t))
(expect (buttercup-suites-total-specs-defined suites) :to-equal 11)
(expect (buttercup-suites-total-specs-pending suites) :to-equal 5)
- (with-local-buttercup
- (setq buttercup-suites suites)
+ (with-local-buttercup :suites suites
(buttercup-run))
(expect (buttercup-suites-total-specs-pending suites) :to-equal 5)
(expect (cl-count "SKIPPED" (buttercup--specs suites)
:key #'buttercup-spec-failure-description)
:to-equal 0))
- (it "should mark all specs as pending with no pattern"
- (buttercup--mark-skipped suites '())
+ (it "should mark all specs as pending with a reversed match none pattern"
+ (with-local-buttercup :suites suites
+ (buttercup-mark-skipped "[z-a]" t))
(expect (buttercup-suites-total-specs-defined suites) :to-equal 11)
(expect (buttercup-suites-total-specs-pending suites) :to-equal 11))
(it "should handle multiple patterns"
- (buttercup--mark-skipped suites '("1-1-[1-2]" "[12]-4"))
+ (with-local-buttercup :suites suites
+ (buttercup-mark-skipped '("1-1-1" "1-1-2" "1-4" "2-4") t))
+ (expect (buttercup-suites-total-specs-defined suites) :to-equal 11)
+ (expect (buttercup-suites-total-specs-pending suites) :to-equal 8))
+ (it "should support predicates"
+ (with-local-buttercup :suites suites
+ (buttercup-mark-skipped
+ (lambda (spec) (= 2 (cl-count ?- (buttercup-spec-full-name spec))))))
(expect (buttercup-suites-total-specs-defined suites) :to-equal 11)
- (expect (buttercup-suites-total-specs-pending suites) :to-equal 8)))
+ (expect (buttercup-suites-total-specs-pending suites) :to-equal 10))
+ (it "should support reversed predicates"
+ (with-local-buttercup :suites suites
+ (buttercup-mark-skipped
+ (lambda (spec) (= 2 (cl-count ?- (buttercup-spec-full-name spec))))
+ t))
+ (expect (buttercup-suites-total-specs-defined suites) :to-equal 11)
+ (expect (buttercup-suites-total-specs-pending suites) :to-equal 6))
+ (it "should signal an error for invalid matchers"
+ (with-local-buttercup
+ (expect (buttercup-mark-skipped 4) :to-throw))
+ (with-local-buttercup
+ (expect (buttercup-mark-skipped (list "re" "re" 5 "re")) :to-throw)))
+ )
;;;;;;;;;;;;;;;;;;;;;
;;; ERT Compatibility
(describe "Buttercup's ERT compatibility wrapper"
- (it "should convert `ert-test-failed' into `buttercup-failed"
+ (it "should convert `ert-test-failed' into `buttercup-failed'"
(expect
(buttercup-with-converted-ert-signals
(should (equal 1 2)))
:to-throw 'buttercup-failed))
- (it "should convert `ert-test-skipped' into `buttercup-pending"
+ (it "should convert `ert-test-skipped' into `buttercup-pending'"
(assume (functionp 'ert-skip) "Loaded ERT version does not provide `ert-skip'")
(expect
(buttercup-with-converted-ert-signals
(ert-skip "Skipped this test"))
:to-throw 'buttercup-pending)))
+;;;;;;;;;;;;;;;;;;
+;;; test discovery
+
+(describe "`buttercup-run-discover' should"
+ (describe "parse command line arguments"
+ (before-each
+ (spy-on 'buttercup-run)
+ (spy-on 'buttercup-mark-skipped)
+ (spy-on 'directory-files-recursively)
+ (spy-on 'buttercup-error-on-stale-elc))
+ (it "ignoring `--'"
+ (let ((command-line-args-left '("--")))
+ (buttercup-run-discover)
+ (expect command-line-args-left :to-equal nil)))
+ (it "requiring an extra argument for `--traceback'"
+ (let ((command-line-args-left '("--traceback")))
+ (expect (buttercup-run-discover) :to-throw 'error '("Option requires argument: --traceback"))))
+ (it "checking `--traceback' argument for validity"
+ (let ((command-line-args-left '("--traceback" "unknown")))
+ (with-local-buttercup
+ (expect (buttercup-run-discover) :to-throw 'error '("Unknown stack trace style: unknown")))))
+ (it "setting `buttercup-stack-frame-style' from `--traceback' arg"
+ (let ((command-line-args-left '("--traceback" "full")))
+ (with-local-buttercup
+ (buttercup-run-discover)
+ (expect buttercup-stack-frame-style :to-equal 'full))
+ (expect command-line-args-left :to-equal nil)))
+ (it "requiring an extra argument for `--pattern' or `-p'"
+ (let ((command-line-args-left '("--pattern")))
+ (expect (buttercup-run-discover) :to-throw 'error '("Option requires argument: --pattern"))
+ (setq command-line-args-left '("-p"))
+ (expect (buttercup-run-discover) :to-throw 'error '("Option requires argument: -p"))))
+ (it "collecting `--pattern' and `-p' args and send to `buttercup-mark-skipped'"
+ (let ((command-line-args-left '("--pattern" "foo" "-p" "bar" "--pattern" "baz"))
+ buttercup-mark-skipped-args)
+ (buttercup-run-discover)
+ (expect command-line-args-left :to-equal nil)
+ (expect 'buttercup-mark-skipped :to-have-been-called-times 1)
+ (setq buttercup-mark-skipped-args (car (spy-calls-args-for 'buttercup-mark-skipped 0)))
+ (expect buttercup-mark-skipped-args :to-have-same-items-as '("foo" "bar" "baz"))))
+ (it "clearing `buttercup-color' if `--no-color' is given"
+ (let ((command-line-args-left '("--no-color"))
+ (buttercup-color t))
+ (buttercup-run-discover)
+ (expect buttercup-color :to-equal nil)
+ (expect command-line-args-left :to-equal nil)
+ (setq command-line-args-left '("-c")
+ buttercup-color t)
+ (buttercup-run-discover)
+ (expect buttercup-color :to-equal nil)
+ (expect command-line-args-left :to-equal nil)))
+ (it "adding `skipped' and `disabled' to quiet statuses if `--no-skip' is given"
+ (let ((command-line-args-left '("--no-skip")))
+ (with-local-buttercup
+ (buttercup-run-discover)
+ (expect buttercup-reporter-batch-quiet-statuses :to-contain 'skipped)
+ (expect buttercup-reporter-batch-quiet-statuses :to-contain 'disabled))
+ (expect command-line-args-left :to-equal nil)))
+ (it "adding `pending' and `passed' to quiet statuses if `--only-error' is given"
+ (let ((command-line-args-left '("--only-error")))
+ (with-local-buttercup
+ (buttercup-run-discover)
+ (expect buttercup-reporter-batch-quiet-statuses :to-contain 'pending)
+ (expect buttercup-reporter-batch-quiet-statuses :to-contain 'passed))
+ (expect command-line-args-left :to-equal nil)))
+ (it "calling `buttercup-error-on-stale-elc' if `--stale-file-error' is given"
+ (let ((command-line-args-left '("--stale-file-error")))
+ (with-local-buttercup
+ (buttercup-run-discover)
+ (expect 'buttercup-error-on-stale-elc :to-have-been-called-times 1)
+ (expect command-line-args-left :to-equal nil))))
+ (it "search any unknown args for test files"
+ (let ((command-line-args-left '("foo" "--traceback" "full" "bar" "--strange" "baz")))
+ (with-local-buttercup
+ (buttercup-run-discover)
+ (expect 'directory-files-recursively :to-have-been-called-times 4)
+ (expect 'directory-files-recursively :to-have-been-called-with "foo" "\\`test-.*\\.el\\'\\|-tests?\\.el\\'")
+ (expect 'directory-files-recursively :to-have-been-called-with "bar" "\\`test-.*\\.el\\'\\|-tests?\\.el\\'")
+ (expect 'directory-files-recursively :to-have-been-called-with "--strange" "\\`test-.*\\.el\\'\\|-tests?\\.el\\'")
+ (expect 'directory-files-recursively :to-have-been-called-with "baz" "\\`test-.*\\.el\\'\\|-tests?\\.el\\'"))
+ (expect command-line-args-left :to-equal nil)))
+ )
+ (describe "find and load files"
+ (before-each
+ (spy-on 'buttercup-run)
+ (spy-on 'buttercup-mark-skipped)
+ (spy-on 'load)
+ (spy-on 'relative-load-path :and-call-fake
+ (lambda (args)
+ "Return `car' of args relative to `default-directory'."
+ (replace-regexp-in-string
+ (format "^\\(\\./\\|%s\\)" (regexp-quote default-directory))
+ ""
+ (car args))))
+ )
+ (it "named test-*.el and *-tests?.el but no other files"
+ (buttercup--test-with-tempdir
+ '("test.el" "tests.el" "test-actually.el"
+ "foo/test-foo.el" "foo/bar/bar-test.el"
+ "baz/no-test-here.el" "baz/baz-tests.el")
+ (buttercup-run-discover)
+ (expect 'load :to-have-been-called-times 4)
+ (let ((loaded-files (mapcar #'relative-load-path
+ (spy-calls-all-args 'load))))
+ (expect loaded-files :to-have-same-items-as '("test-actually.el"
+ "foo/test-foo.el"
+ "foo/bar/bar-test.el"
+ "baz/baz-tests.el")))))
+ (it "only in given directories"
+ (buttercup--test-with-tempdir
+ '("root-tests.el"
+ "a/a-tests.el" "a/b/ab-tests.el"
+ "b/b-tests-el" "b/a/ba-tests.el")
+ (let ((command-line-args-left '("a")))
+ (buttercup-run-discover))
+ (expect 'load :to-have-been-called-times 2)
+ (let ((loaded-files (mapcar #'relative-load-path (spy-calls-all-args 'load))))
+ (expect loaded-files :to-have-same-items-as '("a/a-tests.el"
+ "a/b/ab-tests.el")))))))
+
;;;;;;;;;;;;;
;;; Utilities
@@ -1482,6 +2007,55 @@ text properties using `ansi-color-apply'."
(expect (length (cdr specs)) :to-equal 1)
(expect (cl-caadr specs) :to-equal "should fontify special keywords")))))
+;;;;;;;;;;;;;;;;;;;
+;;; Stale elc files
+
+(describe "For stale `elc' file checks"
+ (describe "`buttercup-check-for-stale-elc'"
+ :var (el-time elc-time)
+ (before-each
+ (spy-on 'file-attributes :and-call-fake
+ (lambda (filename &optional _id-format)
+ (make-list
+ 10
+ (make-list 4
+ (pcase (file-name-extension filename)
+ ("elc" elc-time)
+ ("el" el-time)))))))
+ (it "should do nothing for `el' files"
+ (setq el-time 2 ;; elc is older than el
+ elc-time 1)
+ (expect (buttercup-check-for-stale-elc "buttercup.el") :not :to-throw))
+ (it "should signal error when `elc' is older than `el'"
+ (setq el-time 2 ;; elc is older than el
+ elc-time 1)
+ (expect (buttercup-check-for-stale-elc "buttercup.elc") :to-throw))
+ (it "should not signal error when `elc' is newer than `el'"
+ (setq el-time 2 ;; elc is older than el
+ elc-time 3)
+ (expect (buttercup-check-for-stale-elc "buttercup.elc") :not :to-throw))
+ (it "should do nothing if the `el' file does not exist"
+ (setq el-time 3 ;; el is older than elc
+ elc-time 2)
+ (spy-on 'file-exists-p)
+ (expect (buttercup-check-for-stale-elc "buttercup.elc") :not :to-throw)))
+
+ (describe "`buttercup-error-on-stale-elc'"
+ (it "should activate with no argument"
+ (let (after-load-functions)
+ (buttercup-error-on-stale-elc)
+ (expect after-load-functions :to-contain 'buttercup-check-for-stale-elc)))
+ (it "should deactivate with almost any argument"
+ (let ((after-load-functions '(buttercup-check-for-stale-elc)))
+ (buttercup-error-on-stale-elc 2)
+ (expect after-load-functions :not :to-contain 'buttercup-check-for-stale-elc)))
+ (it "should toggle when given `toggle' as argument"
+ (let (after-load-functions)
+ (buttercup-error-on-stale-elc 'toggle)
+ (expect after-load-functions :to-contain 'buttercup-check-for-stale-elc)
+ (buttercup-error-on-stale-elc 'toggle)
+ (expect after-load-functions :not :to-contain 'buttercup-check-for-stale-elc)))))
+
;; Local Variables:
;; indent-tabs-mode: nil
;; sentence-end-double-space: nil