emacs-module-tests.el 17.2 KB
Newer Older
1
;;; emacs-module-tests --- Test GNU Emacs modules.  -*- lexical-binding: t; -*-
2

Paul Eggert's avatar
Paul Eggert committed
3
;; Copyright 2015-2020 Free Software Foundation, Inc.
4 5 6 7 8 9 10 11 12 13 14 15 16 17

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
18
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
19

20 21 22 23 24 25 26 27
;;; Commentary:

;; Unit tests for the dynamic module facility.  See Info node `(elisp)
;; Writing Dynamic Modules'.  These tests make use of a small test
;; module in test/data/emacs-module.

;;; Code:

28
(require 'cl-lib)
29
(require 'ert)
30
(require 'help-fns)
31

32 33 34 35 36 37
(defconst mod-test-emacs
  (expand-file-name invocation-name invocation-directory)
  "File name of the Emacs binary currently running.")

(eval-and-compile
  (defconst mod-test-file
38
    (expand-file-name "../test/data/emacs-module/mod-test" invocation-directory)
39 40 41
    "File name of the module test file."))

(require 'mod-test mod-test-file)
42

43 44 45 46 47 48 49 50
(cl-defgeneric emacs-module-tests--generic (_))

(cl-defmethod emacs-module-tests--generic ((_ module-function))
  'module-function)

(cl-defmethod emacs-module-tests--generic ((_ user-ptr))
  'user-ptr)

51
;;
52
;; Basic tests.
53 54 55
;;

(ert-deftest mod-test-sum-test ()
Eli Zaretskii's avatar
Eli Zaretskii committed
56 57 58
  (should (= (mod-test-sum 1 2) 3))
  (let ((descr (should-error (mod-test-sum 1 2 3))))
    (should (eq (car descr) 'wrong-number-of-arguments))
59
    (should (module-function-p (nth 1 descr)))
Eli Zaretskii's avatar
Eli Zaretskii committed
60 61
    (should (eq 0
                (string-match
62
                 (concat "#<module function "
63 64 65
                         "\\(at \\(0x\\)?[[:xdigit:]]+ "
                         "with data 0x1234\\( from .*\\)?"
                         "\\|Fmod_test_sum with data 0x1234 from .*\\)>")
66
                 (prin1-to-string (nth 1 descr)))))
67 68
    (should (= (nth 2 descr) 3)))
  (should-error (mod-test-sum "1" 2) :type 'wrong-type-argument)
69
  (should-error (mod-test-sum 1 "2") :type 'wrong-type-argument)
70
  ;; The following tests are for 32-bit build --with-wide-int.
71 72 73
  (should (= (mod-test-sum -1 most-positive-fixnum)
             (1- most-positive-fixnum)))
  (should (= (mod-test-sum 1 most-negative-fixnum)
74
             (1+ most-negative-fixnum)))
75 76 77
  (when (< #x1fffffff most-positive-fixnum)
    (should (= (mod-test-sum 1 #x1fffffff)
               (1+ #x1fffffff)))
78
    (should (= (mod-test-sum -1 (1+ #x1fffffff))
79
               #x1fffffff)))
Tom Tromey's avatar
Tom Tromey committed
80 81 82 83
  (should (= (mod-test-sum 1 most-positive-fixnum)
             (1+ most-positive-fixnum)))
  (should (= (mod-test-sum -1 most-negative-fixnum)
             (1- most-negative-fixnum))))
84 85

(ert-deftest mod-test-sum-docstring ()
86
  (should (string= (documentation 'mod-test-sum) "Return A + B\n\n(fn a b)")))
87

88 89 90 91 92
(ert-deftest module-function-object ()
  "Extract and test the implementation of a module function.
This test needs to be changed whenever the implementation
changes."
  (let ((func (symbol-function #'mod-test-sum)))
93
    (should (module-function-p func))
94
    (should (functionp func))
95
    (should (equal (type-of func) 'module-function))
96
    (should (eq (emacs-module-tests--generic func) 'module-function))
97 98 99 100
    (should (string-match-p
             (rx bos "#<module function "
                 (or "Fmod_test_sum"
                     (and "at 0x" (+ hex-digit)))
101
                 " with data 0x1234"
102 103 104
                 (? " from " (* nonl) "mod-test" (* nonl) )
                 ">" eos)
             (prin1-to-string func)))))
105

106
;;
107
;; Non-local exists (throw, signal).
108 109 110
;;

(ert-deftest mod-test-non-local-exit-signal-test ()
111 112 113 114 115 116 117 118 119 120 121
  (should-error (mod-test-signal))
  (let (debugger-args backtrace)
    (should-error
     (let ((debugger (lambda (&rest args)
                       (setq debugger-args args
                             backtrace (with-output-to-string (backtrace)))
                       (cl-incf num-nonmacro-input-events)))
           (debug-on-signal t))
       (mod-test-signal)))
    (should (equal debugger-args '(error (error . 56))))
    (should (string-match-p
122
             (rx bol "  mod-test-signal()" eol)
123
             backtrace))))
124 125 126 127 128 129 130 131 132 133 134 135 136

(ert-deftest mod-test-non-local-exit-throw-test ()
  (should (equal
           (catch 'tag
             (mod-test-throw)
             (ert-fail "expected throw"))
           65)))

(ert-deftest mod-test-non-local-exit-funcall-normal ()
  (should (equal (mod-test-non-local-exit-funcall (lambda () 23))
                 23)))

(ert-deftest mod-test-non-local-exit-funcall-signal ()
137 138
  (should (equal (mod-test-non-local-exit-funcall
                  (lambda () (signal 'error '(32))))
139 140 141 142 143 144 145
                 '(signal error (32)))))

(ert-deftest mod-test-non-local-exit-funcall-throw ()
  (should (equal (mod-test-non-local-exit-funcall (lambda () (throw 'tag 32)))
                 '(throw tag 32))))

;;
146
;; String tests.
147 148 149
;;

(defun multiply-string (s n)
150
  "Return N copies of S concatenated together."
151
  (let ((res ""))
152
    (dotimes (_ n)
153 154
      (setq res (concat res s)))
    res))
155 156 157 158 159 160 161

(ert-deftest mod-test-globref-make-test ()
  (let ((mod-str (mod-test-globref-make))
        (ref-str (multiply-string "abcdefghijklmnopqrstuvwxyz" 100)))
    (garbage-collect) ;; XXX: not enough to really test but it's something..
    (should (string= ref-str mod-str))))

162 163 164
(ert-deftest mod-test-globref-free-test ()
  (should (eq (mod-test-globref-free 1 'a "test" 'b) 'ok)))

165 166 167 168
(ert-deftest mod-test-string-a-to-b-test ()
  (should (string= (mod-test-string-a-to-b "aaa") "bbb")))

;;
169
;; User-pointer tests.
170 171 172 173 174 175 176 177
;;

(ert-deftest mod-test-userptr-fun-test ()
  (let* ((n 42)
         (v (mod-test-userptr-make n))
         (r (mod-test-userptr-get v)))

    (should (eq (type-of v) 'user-ptr))
178
    (should (eq (emacs-module-tests--generic v) 'user-ptr))
179 180 181 182 183 184
    (should (integerp r))
    (should (= r n))))

;; TODO: try to test finalizer

;;
185
;; Vector tests.
186 187 188 189 190 191 192 193 194 195 196
;;

(ert-deftest mod-test-vector-test ()
  (dolist (s '(2 10 100 1000))
    (dolist (e '(42 foo "foo"))
      (let* ((v-ref (make-vector 2 e))
             (eq-ref (eq (aref v-ref 0) (aref v-ref 1)))
             (v-test (make-vector s nil)))

        (should (eq (mod-test-vector-fill v-test e) t))
        (should (eq (mod-test-vector-eq v-test e) eq-ref))))))
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211

(ert-deftest module--func-arity ()
  (should (equal (func-arity #'mod-test-return-t) '(1 . 1)))
  (should (equal (func-arity #'mod-test-sum) '(2 . 2))))

(ert-deftest module--help-function-arglist ()
  (should (equal (help-function-arglist #'mod-test-return-t :preserve-names)
                 '(arg1)))
  (should (equal (help-function-arglist #'mod-test-return-t)
                 '(arg1)))
  (should (equal (help-function-arglist #'mod-test-sum :preserve-names)
                 '(a b)))
  (should (equal (help-function-arglist #'mod-test-sum)
                 '(arg1 arg2))))

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
(defmacro module--with-temp-directory (name &rest body)
  "Bind NAME to the name of a temporary directory and evaluate BODY.
NAME must be a symbol.  Delete the temporary directory after BODY
exits normally or non-locally.  NAME will be bound to the
directory name (not the directory file name) of the temporary
directory."
  (declare (indent 1))
  (cl-check-type name symbol)
  `(let ((,name (file-name-as-directory
                 (make-temp-file "emacs-module-test" :directory))))
     (unwind-protect
         (progn ,@body)
       (delete-directory ,name :recursive))))

(defmacro module--test-assertion (pattern &rest body)
  "Test that PATTERN matches the assertion triggered by BODY.
Run Emacs as a subprocess, load the test module `mod-test-file',
and evaluate BODY.  Verify that Emacs aborts and prints a module
assertion message that matches PATTERN.  PATTERN is evaluated and
must evaluate to a regular expression string."
  (declare (indent 1))
  ;; To contain any core dumps.
  `(module--with-temp-directory tempdir
     (with-temp-buffer
       (let* ((default-directory tempdir)
              (status (call-process mod-test-emacs nil t nil
238 239 240
                                    "-batch" "-Q" "-module-assertions"
                                    "-eval" "(setq w32-disable-abort-dialog t)"
                                    "-eval"
241 242 243 244
                                    ,(prin1-to-string
                                      `(progn
                                         (require 'mod-test ,mod-test-file)
                                         ,@body)))))
245 246 247 248 249 250 251 252 253
         ;; Aborting doesn't raise a signal on MS-DOS/Windows, but
         ;; rather exits with a non-zero status: 2 on MS-DOS (see
         ;; msdos.c:msdos_abort), 3 on Windows, per MSDN documentation
         ;; of 'abort'.
         (if (memq system-type '(ms-dos windows-nt))
             (should (>= status 2))
           (should (stringp status))
           ;; eg "Aborted" or "Abort trap: 6"
           (should (string-prefix-p "Abort" status)))
254 255 256 257 258 259 260
         (search-backward "Emacs module assertion: ")
         (goto-char (match-end 0))
         (should (string-match-p ,pattern
                                 (buffer-substring-no-properties
                                  (point) (point-max))))))))

(ert-deftest module--test-assertions--load-non-live-object ()
261
  "Check that -module-assertions verify that non-live objects aren't accessed."
262 263 264
  (skip-unless (or (file-executable-p mod-test-emacs)
                   (and (eq system-type 'windows-nt)
                        (file-executable-p (concat mod-test-emacs ".exe")))))
265
  ;; This doesn't yet cause undefined behavior.
266
  (should (eq (mod-test-invalid-store) 123))
267 268
  (module--test-assertion (rx "Emacs value not found in "
                              (+ digit) " values of "
269
                              (+ digit) " environments\n")
270 271 272 273 274 275 276 277
    ;; Storing and reloading a local value causes undefined behavior,
    ;; which should be detected by the module assertions.
    (mod-test-invalid-store)
    (mod-test-invalid-load)))

(ert-deftest module--test-assertions--call-emacs-from-gc ()
  "Check that -module-assertions prevents calling Emacs functions
during garbage collection."
278 279 280
  (skip-unless (or (file-executable-p mod-test-emacs)
                   (and (eq system-type 'windows-nt)
                        (file-executable-p (concat mod-test-emacs ".exe")))))
281
  (module--test-assertion
282
      (rx "Module function called during garbage collection\n")
283 284
    (mod-test-invalid-finalizer)
    (garbage-collect)))
285

286 287 288 289 290
(ert-deftest module/describe-function-1 ()
  "Check that Bug#30163 is fixed."
  (with-temp-buffer
    (let ((standard-output (current-buffer)))
      (describe-function-1 #'mod-test-sum)
291 292 293
      (goto-char (point-min))
      (while (re-search-forward "`[^']*/data/emacs-module/" nil t)
        (replace-match "`data/emacs-module/"))
294 295 296
      (should (equal
               (buffer-substring-no-properties 1 (point-max))
               (format "a module function in `data/emacs-module/mod-test%s'.
297 298 299

(mod-test-sum a b)

300 301 302 303 304 305 306 307 308 309
Return A + B"
                       module-file-suffix))))))

(ert-deftest module/load-history ()
  "Check that Bug#30164 is fixed."
  (load mod-test-file)
  (cl-destructuring-bind (file &rest entries) (car load-history)
    (should (equal (file-name-sans-extension file) mod-test-file))
    (should (member '(provide . mod-test) entries))
    (should (member '(defun . mod-test-sum) entries))))
310

311 312 313 314 315 316 317 318 319 320 321 322 323 324
(ert-deftest mod-test-sleep-until ()
  "Check that `mod-test-sleep-until' either returns normally or quits.
Interactively, you can try hitting \\[keyboard-quit] to quit."
  (dolist (arg '(nil t))
    ;; Guard against some caller setting `inhibit-quit'.
    (with-local-quit
      (condition-case nil
          (should (eq (with-local-quit
                        ;; Because `inhibit-quit' is nil here, the next
                        ;; form either quits or returns `finished'.
                        (mod-test-sleep-until
                         ;; Interactively, run for 5 seconds to give the
                         ;; user time to quit.  In batch mode, run only
                         ;; briefly since the user can't quit.
325
                         (time-add nil (if noninteractive 0.1 5))
326 327 328 329 330
                         ;; should_quit or process_input
                         arg))
                      'finished))
        (quit)))))

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
(ert-deftest mod-test-add-nanosecond/valid ()
  (dolist (input (list
                  ;; Some realistic examples.
                  (current-time) (time-to-seconds)
                  (encode-time 12 34 5 6 7 2019 t)
                  ;; Various legacy timestamp forms.
                  '(123 456) '(123 456 789) '(123 456 789 6000)
                  ;; Corner case: this will result in a nanosecond
                  ;; value of 1000000000 after addition.  The module
                  ;; code should handle this correctly.
                  '(123 65535 999999 999000)
                  ;; Seconds since the epoch.
                  123 123.45
                  ;; New (TICKS . HZ) format.
                  '(123456789 . 1000000000)))
    (ert-info ((format "input: %s" input))
Paul Eggert's avatar
Paul Eggert committed
347 348 349 350
      (let ((result (mod-test-add-nanosecond input))
	    (desired-result
	     (let ((hz 1000000000))
	       (time-add (time-convert input hz) (cons 1 hz)))))
351 352 353 354
        (should (consp result))
        (should (integerp (car result)))
        (should (integerp (cdr result)))
        (should (cl-plusp (cdr result)))
Paul Eggert's avatar
Paul Eggert committed
355
        (should (time-equal-p result desired-result))))))
356 357 358 359 360 361 362 363 364 365

(ert-deftest mod-test-add-nanosecond/nil ()
  (should (<= (float-time (mod-test-add-nanosecond nil))
              (+ (float-time) 1e-9))))

(ert-deftest mod-test-add-nanosecond/invalid ()
  (dolist (input '(1.0e+INF 1.0e-INF 0.0e+NaN (123) (123.45 6 7) "foo" [1 2]))
    (ert-info ((format "input: %s" input))
      (should-error (mod-test-add-nanosecond input)))))

366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
(ert-deftest mod-test-nanoseconds ()
  "Test truncation when converting to `struct timespec'."
  (dolist (test-case '((0 . 0)
                       (-1 . -1000000000)
                       ((1 . 1000000000) . 1)
                       ((-1 . 1000000000) . -1)
                       ((1 . 1000000000000) . 0)
                       ((-1 . 1000000000000) . -1)
                       ((999 . 1000000000000) . 0)
                       ((-999 . 1000000000000) . -1)
                       ((1000 . 1000000000000) . 1)
                       ((-1000 . 1000000000000) . -1)
                       ((0 0 0 1) . 0)
                       ((0 0 0 -1) . -1)))
    (let ((input (car test-case))
          (expected (cdr test-case)))
      (ert-info ((format "input: %S, expected result: %d" input expected))
383
        (should (= (mod-test-nanoseconds input) expected))))))
384

385 386 387 388 389 390 391
(ert-deftest mod-test-double ()
  (dolist (input (list 0 1 2 -1 42 12345678901234567890
                       most-positive-fixnum (1+ most-positive-fixnum)
                       most-negative-fixnum (1- most-negative-fixnum)))
    (ert-info ((format "input: %d" input))
      (should (= (mod-test-double input) (* 2 input))))))

392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
(ert-deftest module-darwin-secondary-suffix ()
  "Check that on Darwin, both .so and .dylib suffixes work.
See Bug#36226."
  (skip-unless (eq system-type 'darwin))
  (should (member ".dylib" load-suffixes))
  (should (member ".so" load-suffixes))
  ;; Preserve the old `load-history'.  This is needed for some of the
  ;; other unit tests that indirectly rely on `load-history'.
  (let ((load-history load-history)
        (dylib (concat mod-test-file ".dylib"))
        (so (concat mod-test-file ".so")))
    (should (file-regular-p dylib))
    (should-not (file-exists-p so))
    (add-name-to-file dylib so)
    (unwind-protect
        (load so nil nil :nosuffix :must-suffix)
      (delete-file so))))

410
(ert-deftest module/function-finalizer ()
411 412 413 414 415 416 417 418 419 420 421
  "Test that module function finalizers are properly called."
  ;; We create and leak a couple of module functions with attached
  ;; finalizer.  Creating only one function risks spilling it to the
  ;; stack, where it wouldn't be garbage-collected.  However, with one
  ;; hundred functions, there should be at least one that's
  ;; unreachable.
  (dotimes (_ 100)
    (mod-test-make-function-with-finalizer))
  (cl-destructuring-bind (valid-before invalid-before)
      (mod-test-function-finalizer-calls)
    (should (zerop invalid-before))
422
    (garbage-collect)
423 424 425 426 427 428
    (cl-destructuring-bind (valid-after invalid-after)
        (mod-test-function-finalizer-calls)
      (should (zerop invalid-after))
      ;; We don't require exactly 100 invocations of the finalizer,
      ;; but at least one.
      (should (> valid-after valid-before)))))
429

430 431
(ert-deftest module/async-pipe ()
  "Check that writing data from another thread works."
432
  (skip-unless (not (eq system-type 'windows-nt))) ; FIXME!
433 434 435 436 437 438 439 440 441
  (with-temp-buffer
    (let ((process (make-pipe-process :name "module/async-pipe"
                                      :buffer (current-buffer)
                                      :coding 'utf-8-unix
                                      :noquery t)))
      (unwind-protect
          (progn
            (mod-test-async-pipe process)
            (should (accept-process-output process 1))
442 443
            ;; The string below must be identical to what
            ;; mod-test.c:write_to_pipe produces.
444 445 446
            (should (equal (buffer-string) "data from thread")))
        (delete-process process)))))

447
;;; emacs-module-tests.el ends here