1;;; erlang-test.el -*- lexical-binding: t; coding: utf-8-unix -*-
2
3;;; Unit tests for erlang.el.
4
5;; Author: Johan Claesson
6;; Created: 2016-05-07
7;; Keywords: erlang, languages
8
9;; %CopyrightBegin%
10;;
11;; Copyright Ericsson AB 2016-2020. All Rights Reserved.
12;;
13;; Licensed under the Apache License, Version 2.0 (the "License");
14;; you may not use this file except in compliance with the License.
15;; You may obtain a copy of the License at
16;;
17;;     http://www.apache.org/licenses/LICENSE-2.0
18;;
19;; Unless required by applicable law or agreed to in writing, software
20;; distributed under the License is distributed on an "AS IS" BASIS,
21;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22;; See the License for the specific language governing permissions and
23;; limitations under the License.
24;;
25;; %CopyrightEnd%
26
27
28;;; Commentary:
29
30;; This library require GNU Emacs 25 or later.
31;;
32;; There are three ways to run the erlang emacs unit tests.
33;;
34;; 1. Within a running emacs process.  Load this file.  Then to run
35;; all defined test cases:
36;;
37;; M-x ert RET t RET
38;;
39;; To run only the erlang test cases:
40;;
41;; M-x ert RET "^erlang" RET
42;;
43;;
44;; 2. In a new stand-alone emacs process.  This process exits
45;; when it executed the tests.  For example:
46;;
47;; emacs -Q -batch -L . -l erlang.el -l erlang-test.el \
48;;       -f ert-run-tests-batch-and-exit
49;;
50;; The -L option adds a directory to the load-path.  It should be the
51;; directory containing erlang.el and erlang-test.el.
52;;
53;; 3. Run the emacs_SUITE.  The testcases tests_interpreted/1 and
54;; tests_compiled/1 in this suite are using the second method.  One
55;; way to run this suite is with the ct_run tool, for example like the
56;; following when standing at the OTP repo top directory:
57;;
58;; ct_run -suite lib/tools/test/emacs_SUITE
59;;
60;; Note that this creates a lot of html log files in the current
61;; directory.
62
63;;; Code:
64
65(require 'ert)
66(require 'erlang)
67
68(defvar erlang-test-code
69  '((nil . "-module(erlang_test).")
70    (nil . "-import(lists, [map/2]).")
71    (nil . "-compile(export_all).")
72    ("SYMBOL" . "-define(SYMBOL, value).")
73    ("MACRO" . "-define(MACRO(X), X + X).")
74    ("struct" . "-record(struct, {until,maps,are,everywhere}).")
75    ("function" . "function() -> #struct{}."))
76  "Alist of erlang test code.
77Each entry have the format (TAGNAME . ERLANG_CODE).  If TAGNAME
78is nil there is no definitions in the ERLANG_CODE.  The
79ERLANG_CODE is a single line of erlang code.  These lines will be
80concatenated to form an erlang file to test on.")
81
82
83(ert-deftest erlang-test-tags ()
84  (let* ((dir (make-temp-file "erlang-test" t))
85         (erlang-file (expand-file-name "erlang_test.erl" dir))
86         (tags-file (expand-file-name "TAGS" dir))
87         (old-tags-file-name (default-value 'tags-file-name))
88         (old-tags-table-list (default-value 'tags-table-list))
89         tags-file-name
90         tags-table-list
91         tags-table-set-list
92         tags-add-tables
93         tags-completion-table
94         erlang-buffer
95         erlang-mode-hook
96         prog-mode-hook
97         erlang-shell-mode-hook)
98    (unwind-protect
99        (progn
100          (setq-default tags-file-name nil)
101          (setq-default tags-table-list nil)
102          (erlang-test-create-erlang-file erlang-file)
103          (erlang-test-compile-tags erlang-file tags-file)
104          (setq erlang-buffer (find-file-noselect erlang-file))
105          (if (< emacs-major-version 26)
106              (progn
107                (with-current-buffer erlang-buffer
108                  (setq-local tags-file-name tags-file))
109                ;; Setting global tags-file-name is a workaround for
110                ;; GNU Emacs bug#23164.
111                (setq tags-file-name tags-file))
112            (visit-tags-table tags-file t))
113          (erlang-test-complete-at-point tags-file)
114          (erlang-test-completion-table)
115          (erlang-test-xref-find-definitions erlang-file erlang-buffer))
116      (when (buffer-live-p erlang-buffer)
117        (kill-buffer erlang-buffer))
118      (let ((tags-buffer (find-buffer-visiting tags-file)))
119        (when (buffer-live-p tags-buffer)
120          (kill-buffer tags-buffer)))
121      (when (file-exists-p dir)
122        (delete-directory dir t))
123      (setq-default tags-file-name old-tags-file-name)
124      (setq-default tags-table-list old-tags-table-list))))
125
126(defun erlang-test-create-erlang-file (erlang-file)
127  (with-temp-file erlang-file
128    (cl-loop for (_ . code) in erlang-test-code
129             do (insert code "\n"))))
130
131(defun erlang-test-compile-tags (erlang-file tags-file)
132  (should (zerop (call-process "etags" nil nil nil
133                               "-o" tags-file
134                               erlang-file))))
135
136(defun erlang-test-completion-table ()
137  (let ((erlang-replace-etags-tags-completion-table t))
138    (setq tags-completion-table nil)
139    (tags-completion-table))
140  (should (equal (sort tags-completion-table #'string-lessp)
141                 (sort (erlang-expected-completion-table) #'string-lessp))))
142
143(defun erlang-expected-completion-table ()
144  (append (cl-loop for (symbol . _) in erlang-test-code
145                   when (stringp symbol)
146                   append (list symbol (concat "erlang_test:" symbol)))
147          (list "erlang_test:" "erlang_test:module_info")))
148
149(defun erlang-test-xref-find-definitions (erlang-file erlang-buffer)
150  (cl-loop for (tagname . code) in erlang-test-code
151           for line = 1 then (1+ line)
152           do (when tagname
153                (switch-to-buffer erlang-buffer)
154                (erlang-test-xref-jump tagname erlang-file line)
155                (when (string-equal tagname "function")
156                  (erlang-test-xref-jump (concat "erlang_test:" tagname)
157                                         erlang-file line))))
158  (erlang-test-xref-jump "erlang_test:" erlang-file 1))
159
160(defun erlang-test-xref-jump (id expected-file expected-line)
161  (goto-char (point-max))
162  (insert "\n%% " id)
163  (save-buffer)
164  (if (fboundp 'xref-find-definitions)
165      (xref-find-definitions (erlang-id-to-string
166                              (erlang-get-identifier-at-point)))
167    (error "xref-find-definitions not defined (too old emacs?)"))
168  (erlang-test-verify-pos expected-file expected-line))
169
170(defun erlang-test-verify-pos (expected-file expected-line)
171  (should (string-equal (file-truename expected-file)
172                        (file-truename (buffer-file-name))))
173  (should (eq expected-line (line-number-at-pos)))
174  (should (= (point-at-bol) (point))))
175
176(defun erlang-test-complete-at-point (tags-file)
177  (with-temp-buffer
178    (erlang-mode)
179    (setq-local tags-file-name tags-file)
180    (insert "\nerlang_test:fun")
181    (erlang-complete-tag)
182    (should (looking-back "erlang_test:function" (point-at-bol)))
183    (insert "\nfun")
184    (erlang-complete-tag)
185    (should (looking-back "function" (point-at-bol)))
186    (insert "\nerlang_")
187    (erlang-complete-tag)
188    (should (looking-back "erlang_test:" (point-at-bol)))))
189
190
191(ert-deftest erlang-test-compile-options ()
192  (erlang-test-format-opt t
193                          "t")
194  (erlang-test-format-opt nil
195                          "nil")
196  (erlang-test-format-opt (cons 1 2)
197                          "{1, 2}")
198  (erlang-test-format-opt (list 1)
199                          "[1]")
200  (erlang-test-format-opt (list 1 2)
201                          "[1, 2]")
202  (erlang-test-format-opt (list 1 2 3)
203                          "[1, 2, 3]")
204  (erlang-test-format-opt 'symbol
205                          "symbol")
206  (erlang-test-format-opt "string"
207                          "\"string\"")
208  (erlang-test-format-opt []
209                          "{}")
210  (erlang-test-format-opt [1]
211                          "{1}")
212  (erlang-test-format-opt [1 2]
213                          "{1, 2}")
214  (erlang-test-format-opt [1 2 (3 [4 5 6] 7)]
215                          "{1, 2, [3, {4, 5, 6}, 7]}"))
216
217(defun erlang-test-format-opt (elisp &optional expected-erlang)
218  (let ((erlang (inferior-erlang-format-opt elisp)))
219    (message "%s -> %s" elisp erlang)
220    (when expected-erlang
221      (should (equal erlang expected-erlang)))
222    erlang))
223
224
225(ert-deftest erlang-test-parse-id ()
226  (cl-loop for id-string in '("fun/10"
227                              "qualified-function module:fun/10"
228                              "record reko"
229                              "macro _SYMBOL"
230                              "macro MACRO/10"
231                              "module modula"
232                              "macro"
233                              nil)
234           for id-list in '((nil nil "fun" 10)
235                            (qualified-function "module" "fun" 10)
236                            (record nil "reko" nil)
237                            (macro nil "_SYMBOL" nil)
238                            (macro nil "MACRO" 10)
239                            (module nil "modula" nil)
240                            (nil nil "macro" nil)
241                            nil)
242           for id-list2 = (erlang-id-to-list id-string)
243           do (should (equal id-list id-list2))
244           for id-string2 = (erlang-id-to-string id-list)
245           do (should (equal id-string id-string2))
246           collect id-list2))
247
248
249(provide 'erlang-test)
250
251;;; erlang-test.el ends here
252