From a402a7efb02665d7f62a257041d505a00cc06ed7 Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 12 Jul 2023 18:12:32 -0700 Subject: [PATCH] Add unit tests (#150) * Add unit tests * Tests have long lines * Fix CI hooks * Run on Emacs 28 instead of master --- .github/workflows/ci.yml | 10 ++- Makefile | 7 +- scripts/check-line-length.bash | 1 + test/prescient-test.el | 140 +++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 test/prescient-test.el diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5649c69..c0272fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,15 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: {} jobs: ci: runs-on: ubuntu-latest strategy: matrix: - emacs_version: [25, 26, 27, master] + emacs_version: [25, 26, 27, 28] steps: - name: Checkout uses: actions/checkout@v2 @@ -13,4 +17,4 @@ jobs: env: VERSION: ${{ matrix.emacs_version }} run: >- - make docker CMD="make -k compile checkdoc longlines" + make docker CMD="make -k compile checkdoc longlines test" diff --git a/Makefile b/Makefile index 839ffb5..51cffd3 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ help: ## Show this message column -t -s'|' >&2 .PHONY: lint -lint: compile checkdoc longlines ## Run all the linters +lint: compile checkdoc longlines test ## Run all the linters and tests .PHONY: compile compile: ## Byte-compile @@ -44,6 +44,11 @@ checkdoc: ## Check docstring style longlines: ## Check for long lines @scripts/check-line-length.bash +.PHONY: test +test: + $(EMACS) -Q --batch -L . -l ert -l ./test/prescient-test.el \ + --eval "(let ((ert-quiet t)) (ert-run-tests-batch-and-exit))" + .PHONY: clean clean: ## Remove build artifacts @echo "[clean]" *.elc diff --git a/scripts/check-line-length.bash b/scripts/check-line-length.bash index 6621439..675961c 100755 --- a/scripts/check-line-length.bash +++ b/scripts/check-line-length.bash @@ -8,6 +8,7 @@ find=( -name .git -prune -o -name "*.elc" -o -name "*.png" -o + -name prescient-test.el -o -type f -print ) diff --git a/test/prescient-test.el b/test/prescient-test.el new file mode 100644 index 0000000..a9254f3 --- /dev/null +++ b/test/prescient-test.el @@ -0,0 +1,140 @@ +;;; prescient-test.el --- ERT tests -*- lexical-binding: t -*- + +(require 'prescient) + +;; Stolen from straight.el, credit to @progfolio +;; https://github.com/radian-software/straight.el/blob/ff63b154bef1ef8d92c141bd189001bff74f6982/tests/straight-test.el#L10-L74 + +(eval-and-compile + (defmacro prescient-test--template (template &optional vars &rest bindings) + "Return a list of filled TEMPLATEs. +TEMPLATE is an implicitly backquoted form. +VARS should be a list of symbols denoting the destructuring pattern +for BINDINGS." + (declare (indent 1)) + (if (or (null vars) (null bindings)) + (list template) + (let ((unbound (mod (length bindings) (length vars)))) + (unless (zerop unbound) + (error "Unven binding list: %S" (last bindings unbound))) + (let ((body nil) + (bindings + (eval + `(cl-loop for ,vars on ',bindings + by (lambda (l) (nthcdr ,(length vars) l)) + collect + (apply #'append + (cl-mapcar #'list ',vars (list ,@vars))))))) + (dolist (env bindings (mapcar (lambda (it) (eval it t)) + (nreverse body))) + (unless (mod (length env) 2) (error "Uneven binding list: %S" env)) + (let (e) + (cl-loop for (var val) on env by #'cddr + do (push (list var `(quote ,val)) e)) + (push `(let* ,(nreverse e) (backquote ,template)) body)))))))) + +(cl-defmacro prescient-deftest (object + (&key before-each after-each expected-result + doc tags &allow-other-keys) + &rest template) + "Return auto-tagged and documented `ert-deftest' for OBJECT with TEMPLATE." + (declare (indent defun)) + (let ((counter 0) + (autotags + (delq nil + (list + object + (if (string-match-p "--" (symbol-name object)) + 'private 'public) + (if (macrop object) 'macro)))) + (tests (when template + (macroexpand `(prescient-test--template ,@template))))) + (setq tags (append autotags tags)) + `(progn + ,@(mapcar + (lambda (test) + `(ert-deftest + ,(intern (concat + (format "%s/test" object) + (when (> (length tests) 1) + (format "@%d" (cl-incf counter))))) + () + ,(or doc (when (fboundp object) (documentation object))) + ,@(when tags `(:tags ',tags)) + ,@(when expected-result `(:expected-result ,expected-result)) + ,@(when before-each (if (cl-every #'listp before-each) + before-each + (list before-each))) + ,test + ,@(when after-each (if (cl-every #'listp after-each) + after-each + (list after-each))))) + tests)))) + +(font-lock-add-keywords + nil + '(("(\\(\\\\s *\\(\\(?:\\sw\\|\\s_\\)+\\)?" + (1 font-lock-keyword-face nil t) + (2 font-lock-function-name-face nil t)))) + +;;; Hacks and utilities + +(defmacro prescient-test--stateless (&rest body) + "Exec BODY ignoring existing recency data." + (declare (indent 0)) + `(let ((prescient--history (make-hash-table :test 'equal)) + (prescient--frequency (make-hash-table :test 'equal))) + ,@body)) + +;;; Begin tests + +(prescient-deftest prescient-split-query () + (should (equal ,result (prescient-split-query ,query))) + (query result) + "foo" '("foo") + "foo bar" '("foo" "bar") + "foo bar" '("foo bar") + "foo bar" '("foo bar") + " foo bar " '("foo" "bar") + " foo bar " '(" foo" "bar ") + " foo bar " '(" foo bar ") + "foo bar baz" '("foo" "bar" "baz") + "foo bar baz" '("foo bar" "baz") + "foo bar baz" '("foo" "bar baz") + ) + +(prescient-deftest prescient-filter-regexps () + (let ((prescient-filter-method ,methods) + ;; Simplify the regexes in the test cases by disabling char + ;; folding + (prescient-use-char-folding nil)) + (should (equal ,result (prescient-filter-regexps ,query ,with-group)))) + (methods with-group separated query result) + '(literal) nil nil "foo" '("foo") + '(literal) nil nil "foo bar" '("foo" "bar") + '(literal) nil nil "foo bar" '("foo bar") + '(literal) t nil "hello" '("hello") + '(literal) 'all nil "hello" '("\\(hello\\)") + '(literal) 'all nil "hello world" '("\\(hello\\)" "\\(world\\)") + '(literal) nil nil "**[amaze]**" '("\\*\\*\\[amaze]\\*\\*") + '(initialism) nil nil "hai" '("\\bh\\w*\\W*\\ba\\w*\\W*\\bi\\w*") + '(initialism) t nil "hai" '("\\b\\(h\\)\\w*\\W*\\b\\(a\\)\\w*\\W*\\b\\(i\\)\\w*") + '(literal initialism) nil nil "hai" '("hai\\|\\bh\\w*\\W*\\ba\\w*\\W*\\bi\\w*") + '(literal initialism) t nil "hai" '("hai\\|\\b\\(h\\)\\w*\\W*\\b\\(a\\)\\w*\\W*\\b\\(i\\)\\w*") + '(literal initialism) 'all nil "hai" '("\\(hai\\)\\|\\b\\(h\\)\\w*\\W*\\b\\(a\\)\\w*\\W*\\b\\(i\\)\\w*") + ;; '(literal initialism) nil t "hai" '("hai" "\\bh\\w*\\W*\\ba\\w*\\W*\\bi\\w*") + '(literal regexp) nil nil "f.*d b[o]*t" '("f\\.\\*d\\|f.*d" "b\\[o]\\*t\\|b[o]*t") + ;; '(literal regexp) nil t "f.*d b[o]*t" '("f\\.\\*d" "f.*d" "b\\[o]\\*t" "b[o]*t") + ) + +(prescient-deftest prescient-sort-full-matches-first () + (let ((prescient-filter-method ,methods) + (prescient-sort-full-matches-first ,full-match-first)) + (prescient-test--stateless + (should (equal ,result (prescient-completion-sort + (prescient-filter ,query ,candidates)))))) + (candidates methods full-match-first query result) + '("rebar" "restart-emacs" "barracuda") '(literal initialism) nil "bar" '("rebar" "barracuda") + '("rebar" "restart-emacs" "barracuda") '(literal initialism) nil "re" '("rebar" "restart-emacs") + ;; '("rebar" "restart-emacs" "barracuda") '(literal initialism) t "re" '("restart-emacs" "rebar") + )