;;; erlang-test.el -*- lexical-binding: t; coding: utf-8-unix -*-
;;; Unit tests for erlang.el.
;; Author: Johan Claesson
;; Created: 2016-05-07
;; Keywords: erlang, languages
;; %CopyrightBegin%
;;
;; Copyright Ericsson AB 2016-2017. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
;;
;; %CopyrightEnd%
;;; Commentary:
;; This library require GNU Emacs 25 or later.
;;
;; There are three ways to run the erlang emacs unit tests.
;;
;; 1. Within a running emacs process. Load this file. Then to run
;; all defined test cases:
;;
;; M-x ert RET t RET
;;
;; To run only the erlang test cases:
;;
;; M-x ert RET "^erlang" RET
;;
;;
;; 2. In a new stand-alone emacs process. This process exits
;; when it executed the tests. For example:
;;
;; emacs -Q -batch -L . -l erlang.el -l erlang-test.el \
;; -f ert-run-tests-batch-and-exit
;;
;; The -L option adds a directory to the load-path. It should be the
;; directory containing erlang.el and erlang-test.el.
;;
;; 3. Call the script test-erlang-mode in this directory. This script
;; use the second method.
;;; Code:
(eval-when-compile
(require 'cl))
(require 'ert)
(require 'erlang)
(defvar erlang-test-code
'((nil . "-module(erlang_test).")
(nil . "-import(lists, [map/2]).")
(nil . "-compile(export_all).")
("SYMBOL" . "-define(SYMBOL, value).")
("MACRO" . "-define(MACRO(X), X + X).")
("struct" . "-record(struct, {until,maps,are,everywhere}).")
("function" . "function() -> #struct{}."))
"Alist of erlang test code.
Each entry have the format (TAGNAME . ERLANG_CODE). If TAGNAME
is nil there is no definitions in the ERLANG_CODE. The
ERLANG_CODE is a single line of erlang code. These lines will be
concatenated to form an erlang file to test on.")
(ert-deftest erlang-test-tags ()
(let* ((dir (make-temp-file "erlang-test" t))
(erlang-file (expand-file-name "erlang_test.erl" dir))
(tags-file (expand-file-name "TAGS" dir))
(old-tags-file-name (default-value 'tags-file-name))
(old-tags-table-list (default-value 'tags-table-list))
tags-file-name
tags-table-list
tags-table-set-list
tags-add-tables
tags-completion-table
erlang-buffer
erlang-mode-hook
prog-mode-hook
erlang-shell-mode-hook)
(unwind-protect
(progn
(setq-default tags-file-name nil)
(setq-default tags-table-list nil)
(erlang-test-create-erlang-file erlang-file)
(erlang-test-compile-tags erlang-file tags-file)
(setq erlang-buffer (find-file-noselect erlang-file))
(if (< emacs-major-version 26)
(progn
(with-current-buffer erlang-buffer
(setq-local tags-file-name tags-file))
;; Setting global tags-file-name is a workaround for
;; GNU Emacs bug#23164.
(setq tags-file-name tags-file))
(visit-tags-table tags-file t))
(erlang-test-complete-at-point tags-file)
(erlang-test-completion-table)
(erlang-test-xref-find-definitions erlang-file erlang-buffer))
(when (buffer-live-p erlang-buffer)
(kill-buffer erlang-buffer))
(let ((tags-buffer (find-buffer-visiting tags-file)))
(when (buffer-live-p tags-buffer)
(kill-buffer tags-buffer)))
(when (file-exists-p dir)
(delete-directory dir t))
(setq-default tags-file-name old-tags-file-name)
(setq-default tags-table-list old-tags-table-list))))
(defun erlang-test-create-erlang-file (erlang-file)
(with-temp-file erlang-file
(loop for (_ . code) in erlang-test-code
do (insert code "\n"))))
(defun erlang-test-compile-tags (erlang-file tags-file)
(should (zerop (call-process "etags" nil nil nil
"-o" tags-file
erlang-file))))
(defun erlang-test-completion-table ()
(let ((erlang-replace-etags-tags-completion-table t))
(setq tags-completion-table nil)
(tags-completion-table))
(should (equal (sort tags-completion-table #'string-lessp)
(sort (erlang-expected-completion-table) #'string-lessp))))
(defun erlang-expected-completion-table ()
(append (loop for (symbol . _) in erlang-test-code
when (stringp symbol)
append (list symbol (concat "erlang_test:" symbol)))
(list "erlang_test:" "erlang_test:module_info")))
(defun erlang-test-xref-find-definitions (erlang-file erlang-buffer)
(loop for (tagname . code) in erlang-test-code
for line = 1 then (1+ line)
do (when tagname
(switch-to-buffer erlang-buffer)
(erlang-test-xref-jump tagname erlang-file line)
(when (string-equal tagname "function")
(erlang-test-xref-jump (concat "erlang_test:" tagname)
erlang-file line))))
(erlang-test-xref-jump "erlang_test:" erlang-file 1))
(defun erlang-test-xref-jump (id expected-file expected-line)
(goto-char (point-max))
(insert "\n%% " id)
(save-buffer)
(if (fboundp 'xref-find-definitions)
(xref-find-definitions (erlang-id-to-string
(erlang-get-identifier-at-point)))
(error "xref-find-definitions not defined (too old emacs?)"))
(erlang-test-verify-pos expected-file expected-line))
(defun erlang-test-verify-pos (expected-file expected-line)
(should (string-equal (file-truename expected-file)
(file-truename (buffer-file-name))))
(should (eq expected-line (line-number-at-pos)))
(should (= (point-at-bol) (point))))
(defun erlang-test-complete-at-point (tags-file)
(with-temp-buffer
(erlang-mode)
(setq-local tags-file-name tags-file)
(insert "\nerlang_test:fun")
(erlang-complete-tag)
(should (looking-back "erlang_test:function" (point-at-bol)))
(insert "\nfun")
(erlang-complete-tag)
(should (looking-back "function" (point-at-bol)))
(insert "\nerlang_")
(erlang-complete-tag)
(should (looking-back "erlang_test:" (point-at-bol)))))
(ert-deftest erlang-test-compile-options ()
(erlang-test-format-opt t
"t")
(erlang-test-format-opt nil
"nil")
(erlang-test-format-opt (cons 1 2)
"{1, 2}")
(erlang-test-format-opt (list 1)
"[1]")
(erlang-test-format-opt (list 1 2)
"[1, 2]")
(erlang-test-format-opt (list 1 2 3)
"[1, 2, 3]")
(erlang-test-format-opt 'symbol
"symbol")
(erlang-test-format-opt "string"
"\"string\"")
(erlang-test-format-opt []
"{}")
(erlang-test-format-opt [1]
"{1}")
(erlang-test-format-opt [1 2]
"{1, 2}")
(erlang-test-format-opt [1 2 (3 [4 5 6] 7)]
"{1, 2, [3, {4, 5, 6}, 7]}"))
(defun erlang-test-format-opt (elisp &optional expected-erlang)
(let ((erlang (inferior-erlang-format-opt elisp)))
(message "%s -> %s" elisp erlang)
(when expected-erlang
(should (equal erlang expected-erlang)))
erlang))
(ert-deftest erlang-test-parse-id ()
(loop for id-string in '("fun/10"
"qualified-function module:fun/10"
"record reko"
"macro _SYMBOL"
"macro MACRO/10"
"module modula"
"macro"
nil)
for id-list in '((nil nil "fun" 10)
(qualified-function "module" "fun" 10)
(record nil "reko" nil)
(macro nil "_SYMBOL" nil)
(macro nil "MACRO" 10)
(module nil "modula" nil)
(nil nil "macro" nil)
nil)
for id-list2 = (erlang-id-to-list id-string)
do (should (equal id-list id-list2))
for id-string2 = (erlang-id-to-string id-list)
do (should (equal id-string id-string2))
collect id-list2))
(provide 'erlang-test)
;;; erlang-test.el ends here