diff options
Diffstat (limited to 'test/src/process-tests.el')
| -rw-r--r-- | test/src/process-tests.el | 991 |
1 files changed, 924 insertions, 67 deletions
diff --git a/test/src/process-tests.el b/test/src/process-tests.el index b26f9391909..7d3d9eb72b8 100644 --- a/test/src/process-tests.el +++ b/test/src/process-tests.el | |||
| @@ -1,19 +1,21 @@ | |||
| 1 | ;;; process-tests.el --- Testing the process facilities | 1 | ;;; process-tests.el --- Testing the process facilities -*- lexical-binding: t -*- |
| 2 | 2 | ||
| 3 | ;; Copyright (C) 2013-2017 Free Software Foundation, Inc. | 3 | ;; Copyright (C) 2013-2022 Free Software Foundation, Inc. |
| 4 | 4 | ||
| 5 | ;; This program is free software; you can redistribute it and/or modify | 5 | ;; This file is part of GNU Emacs. |
| 6 | |||
| 7 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 6 | ;; it under the terms of the GNU General Public License as published by | 8 | ;; it under the terms of the GNU General Public License as published by |
| 7 | ;; the Free Software Foundation, either version 3 of the License, or | 9 | ;; the Free Software Foundation, either version 3 of the License, or |
| 8 | ;; (at your option) any later version. | 10 | ;; (at your option) any later version. |
| 9 | 11 | ||
| 10 | ;; This program is distributed in the hope that it will be useful, | 12 | ;; GNU Emacs is distributed in the hope that it will be useful, |
| 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | ;; GNU General Public License for more details. | 15 | ;; GNU General Public License for more details. |
| 14 | 16 | ||
| 15 | ;; You should have received a copy of the GNU General Public License | 17 | ;; You should have received a copy of the GNU General Public License |
| 16 | ;; along with this program. If not, see <https://www.gnu.org/licenses/>. | 18 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. |
| 17 | 19 | ||
| 18 | ;;; Commentary: | 20 | ;;; Commentary: |
| 19 | 21 | ||
| @@ -21,61 +23,74 @@ | |||
| 21 | 23 | ||
| 22 | ;;; Code: | 24 | ;;; Code: |
| 23 | 25 | ||
| 26 | (require 'cl-lib) | ||
| 24 | (require 'ert) | 27 | (require 'ert) |
| 28 | (require 'ert-x) ; ert-with-temp-directory | ||
| 29 | (require 'puny) | ||
| 30 | (require 'subr-x) | ||
| 31 | (require 'dns) | ||
| 32 | (require 'url-http) | ||
| 33 | |||
| 34 | (declare-function thread-last-error "thread.c") | ||
| 35 | (declare-function thread-join "thread.c") | ||
| 36 | (declare-function make-thread "thread.c") | ||
| 25 | 37 | ||
| 26 | ;; Timeout in seconds; the test fails if the timeout is reached. | 38 | ;; Timeout in seconds; the test fails if the timeout is reached. |
| 27 | (defvar process-test-sentinel-wait-timeout 2.0) | 39 | (defvar process-test-sentinel-wait-timeout 2.0) |
| 28 | 40 | ||
| 29 | ;; Start a process that exits immediately. Call WAIT-FUNCTION, | 41 | (defun process-test-wait-for-sentinel (proc exit-status &optional wait-function) |
| 30 | ;; possibly multiple times, to wait for the process to complete. | 42 | "Set a sentinel on PROC and wait for it to be called with EXIT-STATUS. |
| 31 | (defun process-test-sentinel-wait-function-working-p (wait-function) | 43 | Call WAIT-FUNCTION, possibly multiple times, to wait for the |
| 32 | (let ((proc (start-process "test" nil "bash" "-c" "exit 20")) | 44 | process to complete." |
| 45 | (let ((wait-function (or wait-function #'accept-process-output)) | ||
| 33 | (sentinel-called nil) | 46 | (sentinel-called nil) |
| 34 | (start-time (float-time))) | 47 | (start-time (float-time))) |
| 35 | (set-process-sentinel proc (lambda (proc msg) | 48 | (set-process-sentinel proc (lambda (_proc _msg) |
| 36 | (setq sentinel-called t))) | 49 | (setq sentinel-called t))) |
| 37 | (while (not (or sentinel-called | 50 | (while (not (or sentinel-called |
| 38 | (> (- (float-time) start-time) | 51 | (> (- (float-time) start-time) |
| 39 | process-test-sentinel-wait-timeout))) | 52 | process-test-sentinel-wait-timeout))) |
| 40 | (funcall wait-function)) | 53 | (funcall wait-function)) |
| 41 | (cl-assert (eq (process-status proc) 'exit)) | 54 | (should sentinel-called) |
| 42 | (cl-assert (= (process-exit-status proc) 20)) | 55 | (should (eq (process-status proc) 'exit)) |
| 43 | sentinel-called)) | 56 | (should (= (process-exit-status proc) exit-status)))) |
| 44 | 57 | ||
| 45 | (ert-deftest process-test-sentinel-accept-process-output () | 58 | (ert-deftest process-test-sentinel-accept-process-output () |
| 46 | (skip-unless (executable-find "bash")) | 59 | (skip-unless (executable-find "bash")) |
| 47 | (should (process-test-sentinel-wait-function-working-p | 60 | (with-timeout (60 (ert-fail "Test timed out")) |
| 48 | #'accept-process-output))) | 61 | (let ((proc (start-process "test" nil "bash" "-c" "exit 20"))) |
| 62 | (should (process-test-wait-for-sentinel proc 20))))) | ||
| 49 | 63 | ||
| 50 | (ert-deftest process-test-sentinel-sit-for () | 64 | (ert-deftest process-test-sentinel-sit-for () |
| 51 | (skip-unless (executable-find "bash")) | 65 | (skip-unless (executable-find "bash")) |
| 52 | (should | 66 | (with-timeout (60 (ert-fail "Test timed out")) |
| 53 | (process-test-sentinel-wait-function-working-p (lambda () (sit-for 0.01 t))))) | 67 | (let ((proc (start-process "test" nil "bash" "-c" "exit 20"))) |
| 68 | (should (process-test-wait-for-sentinel | ||
| 69 | proc 20 (lambda () (sit-for 0.01 t))))))) | ||
| 54 | 70 | ||
| 55 | (when (eq system-type 'windows-nt) | 71 | (when (eq system-type 'windows-nt) |
| 56 | (ert-deftest process-test-quoted-batfile () | 72 | (ert-deftest process-test-quoted-batfile () |
| 57 | "Check that Emacs hides CreateProcess deficiency (bug#18745)." | 73 | "Check that Emacs hides CreateProcess deficiency (bug#18745)." |
| 58 | (let (batfile) | 74 | (ert-with-temp-file batfile |
| 59 | (unwind-protect | 75 | ;; CreateProcess will fail when both the bat file and 1st |
| 60 | (progn | 76 | ;; argument are quoted, so include spaces in both of those |
| 61 | ;; CreateProcess will fail when both the bat file and 1st | 77 | ;; to force quoting. |
| 62 | ;; argument are quoted, so include spaces in both of those | 78 | :prefix "echo args" |
| 63 | ;; to force quoting. | 79 | :suffix ".bat" |
| 64 | (setq batfile (make-temp-file "echo args" nil ".bat")) | 80 | (with-temp-file batfile |
| 65 | (with-temp-file batfile | 81 | (insert "@echo arg1=%1, arg2=%2\n")) |
| 66 | (insert "@echo arg1=%1, arg2=%2\n")) | 82 | (with-temp-buffer |
| 67 | (with-temp-buffer | 83 | (call-process batfile nil '(t t) t "x &y") |
| 68 | (call-process batfile nil '(t t) t "x &y") | 84 | (should (string= (buffer-string) "arg1=\"x &y\", arg2=\n"))) |
| 69 | (should (string= (buffer-string) "arg1=\"x &y\", arg2=\n"))) | 85 | (with-temp-buffer |
| 70 | (with-temp-buffer | 86 | (call-process-shell-command |
| 71 | (call-process-shell-command | 87 | (mapconcat #'shell-quote-argument (list batfile "x &y") " ") |
| 72 | (mapconcat #'shell-quote-argument (list batfile "x &y") " ") | 88 | nil '(t t) t) |
| 73 | nil '(t t) t) | 89 | (should (string= (buffer-string) "arg1=\"x &y\", arg2=\n")))))) |
| 74 | (should (string= (buffer-string) "arg1=\"x &y\", arg2=\n")))) | ||
| 75 | (when batfile (delete-file batfile)))))) | ||
| 76 | 90 | ||
| 77 | (ert-deftest process-test-stderr-buffer () | 91 | (ert-deftest process-test-stderr-buffer () |
| 78 | (skip-unless (executable-find "bash")) | 92 | (skip-unless (executable-find "bash")) |
| 93 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 79 | (let* ((stdout-buffer (generate-new-buffer "*stdout*")) | 94 | (let* ((stdout-buffer (generate-new-buffer "*stdout*")) |
| 80 | (stderr-buffer (generate-new-buffer "*stderr*")) | 95 | (stderr-buffer (generate-new-buffer "*stderr*")) |
| 81 | (proc (make-process :name "test" | 96 | (proc (make-process :name "test" |
| @@ -84,28 +99,19 @@ | |||
| 84 | "echo hello stderr! >&2; " | 99 | "echo hello stderr! >&2; " |
| 85 | "exit 20")) | 100 | "exit 20")) |
| 86 | :buffer stdout-buffer | 101 | :buffer stdout-buffer |
| 87 | :stderr stderr-buffer)) | 102 | :stderr stderr-buffer))) |
| 88 | (sentinel-called nil) | 103 | (process-test-wait-for-sentinel proc 20) |
| 89 | (start-time (float-time))) | ||
| 90 | (set-process-sentinel proc (lambda (proc msg) | ||
| 91 | (setq sentinel-called t))) | ||
| 92 | (while (not (or sentinel-called | ||
| 93 | (> (- (float-time) start-time) | ||
| 94 | process-test-sentinel-wait-timeout))) | ||
| 95 | (accept-process-output)) | ||
| 96 | (cl-assert (eq (process-status proc) 'exit)) | ||
| 97 | (cl-assert (= (process-exit-status proc) 20)) | ||
| 98 | (should (with-current-buffer stdout-buffer | 104 | (should (with-current-buffer stdout-buffer |
| 99 | (goto-char (point-min)) | 105 | (goto-char (point-min)) |
| 100 | (looking-at "hello stdout!"))) | 106 | (looking-at "hello stdout!"))) |
| 101 | (should (with-current-buffer stderr-buffer | 107 | (should (with-current-buffer stderr-buffer |
| 102 | (goto-char (point-min)) | 108 | (goto-char (point-min)) |
| 103 | (looking-at "hello stderr!"))))) | 109 | (looking-at "hello stderr!")))))) |
| 104 | 110 | ||
| 105 | (ert-deftest process-test-stderr-filter () | 111 | (ert-deftest process-test-stderr-filter () |
| 106 | (skip-unless (executable-find "bash")) | 112 | (skip-unless (executable-find "bash")) |
| 107 | (let* ((sentinel-called nil) | 113 | (with-timeout (60 (ert-fail "Test timed out")) |
| 108 | (stderr-sentinel-called nil) | 114 | (let* ((stderr-sentinel-called nil) |
| 109 | (stdout-output nil) | 115 | (stdout-output nil) |
| 110 | (stderr-output nil) | 116 | (stderr-output nil) |
| 111 | (stdout-buffer (generate-new-buffer "*stdout*")) | 117 | (stdout-buffer (generate-new-buffer "*stdout*")) |
| @@ -117,36 +123,62 @@ | |||
| 117 | (concat "echo hello stdout!; " | 123 | (concat "echo hello stdout!; " |
| 118 | "echo hello stderr! >&2; " | 124 | "echo hello stderr! >&2; " |
| 119 | "exit 20")) | 125 | "exit 20")) |
| 120 | :stderr stderr-proc)) | 126 | :stderr stderr-proc))) |
| 121 | (start-time (float-time))) | 127 | (set-process-filter proc (lambda (_proc input) |
| 122 | (set-process-filter proc (lambda (proc input) | ||
| 123 | (push input stdout-output))) | 128 | (push input stdout-output))) |
| 124 | (set-process-sentinel proc (lambda (proc msg) | 129 | (set-process-filter stderr-proc (lambda (_proc input) |
| 125 | (setq sentinel-called t))) | ||
| 126 | (set-process-filter stderr-proc (lambda (proc input) | ||
| 127 | (push input stderr-output))) | 130 | (push input stderr-output))) |
| 128 | (set-process-sentinel stderr-proc (lambda (proc input) | 131 | (set-process-sentinel stderr-proc (lambda (_proc _input) |
| 129 | (setq stderr-sentinel-called t))) | 132 | (setq stderr-sentinel-called t))) |
| 130 | (while (not (or sentinel-called | 133 | (process-test-wait-for-sentinel proc 20) |
| 131 | (> (- (float-time) start-time) | ||
| 132 | process-test-sentinel-wait-timeout))) | ||
| 133 | (accept-process-output)) | ||
| 134 | (cl-assert (eq (process-status proc) 'exit)) | ||
| 135 | (cl-assert (= (process-exit-status proc) 20)) | ||
| 136 | (should sentinel-called) | ||
| 137 | (should (equal 1 (with-current-buffer stdout-buffer | 134 | (should (equal 1 (with-current-buffer stdout-buffer |
| 138 | (point-max)))) | 135 | (point-max)))) |
| 139 | (should (equal "hello stdout!\n" | 136 | (should (equal "hello stdout!\n" |
| 140 | (mapconcat #'identity (nreverse stdout-output) ""))) | 137 | (mapconcat #'identity (nreverse stdout-output)))) |
| 141 | (should stderr-sentinel-called) | 138 | (should stderr-sentinel-called) |
| 142 | (should (equal 1 (with-current-buffer stderr-buffer | 139 | (should (equal 1 (with-current-buffer stderr-buffer |
| 143 | (point-max)))) | 140 | (point-max)))) |
| 144 | (should (equal "hello stderr!\n" | 141 | (should (equal "hello stderr!\n" |
| 145 | (mapconcat #'identity (nreverse stderr-output) ""))))) | 142 | (mapconcat #'identity (nreverse stderr-output))))))) |
| 143 | |||
| 144 | (ert-deftest set-process-filter-t () | ||
| 145 | "Test setting process filter to t and back." ;; Bug#36591 | ||
| 146 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 147 | (with-temp-buffer | ||
| 148 | (let* ((print-level nil) | ||
| 149 | (print-length nil) | ||
| 150 | (proc (start-process | ||
| 151 | "test proc" (current-buffer) | ||
| 152 | (concat invocation-directory invocation-name) | ||
| 153 | "-Q" "--batch" "--eval" | ||
| 154 | (prin1-to-string | ||
| 155 | '(let ((s nil) (count 0)) | ||
| 156 | (while (setq s (read-from-minibuffer | ||
| 157 | (format "%d> " count))) | ||
| 158 | (princ s) | ||
| 159 | (princ "\n") | ||
| 160 | (setq count (1+ count)))))))) | ||
| 161 | (set-process-query-on-exit-flag proc nil) | ||
| 162 | (send-string proc "one\n") | ||
| 163 | (while (not (equal (buffer-substring (pos-bol) (point-max)) | ||
| 164 | "1> ")) | ||
| 165 | (accept-process-output proc)) ; Read "one". | ||
| 166 | (should (equal (buffer-string) "0> one\n1> ")) | ||
| 167 | (set-process-filter proc t) ; Stop reading from proc. | ||
| 168 | (send-string proc "two\n") | ||
| 169 | (should-not | ||
| 170 | (accept-process-output proc 1)) ; Can't read "two" yet. | ||
| 171 | (should (equal (buffer-string) "0> one\n1> ")) | ||
| 172 | (set-process-filter proc nil) ; Resume reading from proc. | ||
| 173 | (while (not (equal (buffer-substring (pos-bol) (point-max)) | ||
| 174 | "2> ")) | ||
| 175 | (accept-process-output proc)) ; Read "Two". | ||
| 176 | (should (equal (buffer-string) "0> one\n1> two\n2> ")))))) | ||
| 146 | 177 | ||
| 147 | (ert-deftest start-process-should-not-modify-arguments () | 178 | (ert-deftest start-process-should-not-modify-arguments () |
| 148 | "`start-process' must not modify its arguments in-place." | 179 | "`start-process' must not modify its arguments in-place." |
| 149 | ;; See bug#21831. | 180 | ;; See bug#21831. |
| 181 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 150 | (let* ((path (pcase system-type | 182 | (let* ((path (pcase system-type |
| 151 | ((or 'windows-nt 'ms-dos) | 183 | ((or 'windows-nt 'ms-dos) |
| 152 | ;; Make sure the file name uses forward slashes. | 184 | ;; Make sure the file name uses forward slashes. |
| @@ -160,7 +192,832 @@ | |||
| 160 | (should (process-live-p (condition-case nil | 192 | (should (process-live-p (condition-case nil |
| 161 | (start-process "" nil path) | 193 | (start-process "" nil path) |
| 162 | (error nil)))) | 194 | (error nil)))) |
| 163 | (should (equal path samepath)))) | 195 | (should (equal path samepath))))) |
| 196 | |||
| 197 | (ert-deftest make-process/noquery-stderr () | ||
| 198 | "Checks that Bug#30031 is fixed." | ||
| 199 | (skip-unless (executable-find "sleep")) | ||
| 200 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 201 | (with-temp-buffer | ||
| 202 | (let* ((previous-processes (process-list)) | ||
| 203 | (process (make-process :name "sleep" | ||
| 204 | :command '("sleep" "1h") | ||
| 205 | :noquery t | ||
| 206 | :connection-type 'pipe | ||
| 207 | :stderr (current-buffer)))) | ||
| 208 | (unwind-protect | ||
| 209 | (let ((new-processes (cl-set-difference (process-list) | ||
| 210 | previous-processes | ||
| 211 | :test #'eq))) | ||
| 212 | (should new-processes) | ||
| 213 | (dolist (process new-processes) | ||
| 214 | (should-not (process-query-on-exit-flag process)))) | ||
| 215 | (kill-process process)))))) | ||
| 216 | |||
| 217 | ;; Return t if OUTPUT could have been generated by merging the INPUTS somehow. | ||
| 218 | (defun process-tests--mixable (output &rest inputs) | ||
| 219 | (while (and output (let ((ins inputs)) | ||
| 220 | (while (and ins (not (eq (car (car ins)) (car output)))) | ||
| 221 | (setq ins (cdr ins))) | ||
| 222 | (if ins | ||
| 223 | (setcar ins (cdr (car ins)))) | ||
| 224 | ins)) | ||
| 225 | (setq output (cdr output))) | ||
| 226 | (not (apply #'append output inputs))) | ||
| 227 | |||
| 228 | (ert-deftest make-process/mix-stderr () | ||
| 229 | "Check that `make-process' mixes the output streams if STDERR is nil." | ||
| 230 | (skip-unless (executable-find "bash")) | ||
| 231 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 232 | ;; Frequent random (?) failures on hydra.nixos.org, with no process output. | ||
| 233 | ;; Maybe this test should be tagged unstable? See bug#31214. | ||
| 234 | (skip-unless (not (getenv "EMACS_HYDRA_CI"))) | ||
| 235 | (with-temp-buffer | ||
| 236 | (let ((process (make-process | ||
| 237 | :name "mix-stderr" | ||
| 238 | :command (list "bash" "-c" | ||
| 239 | "echo stdout && echo stderr >&2") | ||
| 240 | :buffer (current-buffer) | ||
| 241 | :sentinel #'ignore | ||
| 242 | :noquery t | ||
| 243 | :connection-type 'pipe))) | ||
| 244 | (while (or (accept-process-output process) | ||
| 245 | (process-live-p process))) | ||
| 246 | (should (eq (process-status process) 'exit)) | ||
| 247 | (should (eq (process-exit-status process) 0)) | ||
| 248 | (should (process-tests--mixable (string-to-list (buffer-string)) | ||
| 249 | (string-to-list "stdout\n") | ||
| 250 | (string-to-list "stderr\n"))))))) | ||
| 251 | |||
| 252 | (ert-deftest make-process-w32-debug-spawn-error () | ||
| 253 | "Check that debugger runs on `make-process' failure (Bug#33016)." | ||
| 254 | (skip-unless (eq system-type 'windows-nt)) | ||
| 255 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 256 | (let* ((debug-on-error t) | ||
| 257 | (have-called-debugger nil) | ||
| 258 | (debugger (lambda (&rest _) | ||
| 259 | (setq have-called-debugger t) | ||
| 260 | ;; Allow entering the debugger later in the same | ||
| 261 | ;; test run, before going back to the command | ||
| 262 | ;; loop. | ||
| 263 | (setq internal-when-entered-debugger -1)))) | ||
| 264 | (should (eq :got-error ;; NOTE: `should-error' would inhibit debugger. | ||
| 265 | (condition-case-unless-debug () | ||
| 266 | ;; Emacs doesn't search for absolute filenames, so | ||
| 267 | ;; the error will be hit in the w32 process spawn | ||
| 268 | ;; code. | ||
| 269 | (make-process :name "test" :command '("c:/No-Such-Command")) | ||
| 270 | (error :got-error)))) | ||
| 271 | (should have-called-debugger)))) | ||
| 272 | |||
| 273 | (defun make-process/test-connection-type (ttys &rest args) | ||
| 274 | "Make a process and check whether its standard streams match TTYS. | ||
| 275 | This calls `make-process', passing ARGS to adjust how the process | ||
| 276 | is created. TTYS should be a list of 3 boolean values, | ||
| 277 | indicating whether the subprocess's stdin, stdout, and stderr | ||
| 278 | should be a TTY, respectively." | ||
| 279 | (declare (indent 1)) | ||
| 280 | (let* (;; MS-Windows doesn't support communicating via pty. | ||
| 281 | (ttys (if (eq system-type 'windows-nt) '(nil nil nil) ttys)) | ||
| 282 | (expected-output (concat (and (nth 0 ttys) "stdin\n") | ||
| 283 | (and (nth 1 ttys) "stdout\n") | ||
| 284 | (and (nth 2 ttys) "stderr\n"))) | ||
| 285 | (stdout-buffer (generate-new-buffer "*stdout*")) | ||
| 286 | (proc (apply | ||
| 287 | #'make-process | ||
| 288 | :name "test" | ||
| 289 | :command (list "sh" "-c" | ||
| 290 | (concat "if [ -t 0 ]; then echo stdin; fi; " | ||
| 291 | "if [ -t 1 ]; then echo stdout; fi; " | ||
| 292 | "if [ -t 2 ]; then echo stderr; fi")) | ||
| 293 | :buffer stdout-buffer | ||
| 294 | args))) | ||
| 295 | (should (eq (and (process-tty-name proc 'stdin) t) (nth 0 ttys))) | ||
| 296 | (should (eq (and (process-tty-name proc 'stdout) t) (nth 1 ttys))) | ||
| 297 | (should (eq (and (process-tty-name proc 'stderr) t) (nth 2 ttys))) | ||
| 298 | (process-test-wait-for-sentinel proc 0) | ||
| 299 | (should (equal (with-current-buffer stdout-buffer (buffer-string)) | ||
| 300 | expected-output)))) | ||
| 301 | |||
| 302 | (ert-deftest make-process/connection-type/pty () | ||
| 303 | (skip-unless (executable-find "sh")) | ||
| 304 | (make-process/test-connection-type '(t t t) | ||
| 305 | :connection-type 'pty)) | ||
| 306 | |||
| 307 | (ert-deftest make-process/connection-type/pty-2 () | ||
| 308 | (skip-unless (executable-find "sh")) | ||
| 309 | (make-process/test-connection-type '(t t t) | ||
| 310 | :connection-type '(pty . pty))) | ||
| 311 | |||
| 312 | (ert-deftest make-process/connection-type/pipe () | ||
| 313 | (skip-unless (executable-find "sh")) | ||
| 314 | (make-process/test-connection-type '(nil nil nil) | ||
| 315 | :connection-type 'pipe)) | ||
| 316 | |||
| 317 | (ert-deftest make-process/connection-type/pipe-2 () | ||
| 318 | (skip-unless (executable-find "sh")) | ||
| 319 | (make-process/test-connection-type '(nil nil nil) | ||
| 320 | :connection-type '(pipe . pipe))) | ||
| 321 | |||
| 322 | (ert-deftest make-process/connection-type/in-pty () | ||
| 323 | (skip-unless (executable-find "sh")) | ||
| 324 | (make-process/test-connection-type '(t nil nil) | ||
| 325 | :connection-type '(pty . pipe))) | ||
| 326 | |||
| 327 | (ert-deftest make-process/connection-type/out-pty () | ||
| 328 | (skip-unless (executable-find "sh")) | ||
| 329 | (make-process/test-connection-type '(nil t t) | ||
| 330 | :connection-type '(pipe . pty))) | ||
| 331 | |||
| 332 | (ert-deftest make-process/connection-type/pty-with-stderr-buffer () | ||
| 333 | (skip-unless (executable-find "sh")) | ||
| 334 | (let ((stderr-buffer (generate-new-buffer "*stderr*"))) | ||
| 335 | (make-process/test-connection-type '(t t nil) | ||
| 336 | :connection-type 'pty :stderr stderr-buffer))) | ||
| 337 | |||
| 338 | (ert-deftest make-process/connection-type/out-pty-with-stderr-buffer () | ||
| 339 | (skip-unless (executable-find "sh")) | ||
| 340 | (let ((stderr-buffer (generate-new-buffer "*stderr*"))) | ||
| 341 | (make-process/test-connection-type '(nil t nil) | ||
| 342 | :connection-type '(pipe . pty) :stderr stderr-buffer))) | ||
| 343 | |||
| 344 | (ert-deftest make-process/file-handler/found () | ||
| 345 | "Check that the `:file-handler’ argument of `make-process’ | ||
| 346 | works as expected if a file name handler is found." | ||
| 347 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 348 | (let ((file-handler-calls 0)) | ||
| 349 | (cl-flet ((file-handler | ||
| 350 | (&rest args) | ||
| 351 | (should (equal default-directory "test-handler:/dir/")) | ||
| 352 | (should (equal args '(make-process :name "name" | ||
| 353 | :command ("/some/binary") | ||
| 354 | :file-handler t))) | ||
| 355 | (cl-incf file-handler-calls) | ||
| 356 | 'fake-process)) | ||
| 357 | (let ((file-name-handler-alist (list (cons (rx bos "test-handler:") | ||
| 358 | #'file-handler))) | ||
| 359 | (default-directory "test-handler:/dir/")) | ||
| 360 | (should (eq (make-process :name "name" | ||
| 361 | :command '("/some/binary") | ||
| 362 | :file-handler t) | ||
| 363 | 'fake-process)) | ||
| 364 | (should (= file-handler-calls 1))))))) | ||
| 365 | |||
| 366 | (ert-deftest make-process/file-handler/not-found () | ||
| 367 | "Check that the `:file-handler’ argument of `make-process’ | ||
| 368 | works as expected if no file name handler is found." | ||
| 369 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 370 | (let ((file-name-handler-alist ()) | ||
| 371 | (default-directory invocation-directory) | ||
| 372 | (program (expand-file-name invocation-name invocation-directory))) | ||
| 373 | (should (processp (make-process :name "name" | ||
| 374 | :command (list program "--version") | ||
| 375 | :file-handler t)))))) | ||
| 376 | |||
| 377 | (ert-deftest make-process/file-handler/disable () | ||
| 378 | "Check `make-process’ works as expected if it shouldn’t use the | ||
| 379 | file name handler." | ||
| 380 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 381 | (let ((file-name-handler-alist (list (cons (rx bos "test-handler:") | ||
| 382 | #'process-tests--file-handler))) | ||
| 383 | (default-directory "test-handler:/dir/") | ||
| 384 | (program (expand-file-name invocation-name invocation-directory))) | ||
| 385 | (should (processp (make-process :name "name" | ||
| 386 | :command (list program "--version"))))))) | ||
| 387 | |||
| 388 | (defun process-tests--file-handler (operation &rest _args) | ||
| 389 | (cl-ecase operation | ||
| 390 | (unhandled-file-name-directory "/") | ||
| 391 | (make-process (ert-fail "file name handler called unexpectedly")))) | ||
| 392 | |||
| 393 | (put #'process-tests--file-handler 'operations | ||
| 394 | '(unhandled-file-name-directory make-process)) | ||
| 395 | |||
| 396 | (ert-deftest make-process/stop () | ||
| 397 | "Check that `make-process' doesn't accept a `:stop' key. | ||
| 398 | See Bug#30460." | ||
| 399 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 400 | (should-error | ||
| 401 | (make-process :name "test" | ||
| 402 | :command (list (expand-file-name invocation-name | ||
| 403 | invocation-directory)) | ||
| 404 | :stop t)))) | ||
| 405 | |||
| 406 | ;; The following tests require working DNS | ||
| 407 | |||
| 408 | ;; This will need updating when IANA assign more IPv6 global ranges. | ||
| 409 | (defun ipv6-is-available () | ||
| 410 | (and (featurep 'make-network-process '(:family ipv6)) | ||
| 411 | (cl-rassoc-if | ||
| 412 | (lambda (elt) | ||
| 413 | (and (eq 9 (length elt)) | ||
| 414 | (= (logand (aref elt 0) #xe000) #x2000))) | ||
| 415 | (network-interface-list)))) | ||
| 416 | |||
| 417 | ;; Check if the Internet seems to be working. Mainly to pacify | ||
| 418 | ;; Debian's CI system. | ||
| 419 | (defvar internet-is-working | ||
| 420 | (progn | ||
| 421 | (require 'dns) | ||
| 422 | (dns-query "google.com"))) | ||
| 423 | |||
| 424 | (ert-deftest lookup-family-specification () | ||
| 425 | "`network-lookup-address-info' should only accept valid family symbols." | ||
| 426 | (skip-unless internet-is-working) | ||
| 427 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 428 | (should-error (network-lookup-address-info "localhost" 'both)) | ||
| 429 | (should (network-lookup-address-info "localhost" 'ipv4)) | ||
| 430 | (when (ipv6-is-available) | ||
| 431 | (should (network-lookup-address-info "localhost" 'ipv6))))) | ||
| 432 | |||
| 433 | (ert-deftest lookup-hints-specification () | ||
| 434 | "`network-lookup-address-info' should only accept valid hints arg." | ||
| 435 | (should-error (network-lookup-address-info "1.1.1.1" nil t)) | ||
| 436 | (should-error (network-lookup-address-info "1.1.1.1" 'ipv4 t)) | ||
| 437 | (should (network-lookup-address-info "1.1.1.1" nil 'numeric)) | ||
| 438 | (should (network-lookup-address-info "1.1.1.1" 'ipv4 'numeric)) | ||
| 439 | (when (ipv6-is-available) | ||
| 440 | (should-error (network-lookup-address-info "::1" nil t)) | ||
| 441 | (should-error (network-lookup-address-info "::1" 'ipv6 't)) | ||
| 442 | (should (network-lookup-address-info "::1" nil 'numeric)) | ||
| 443 | (should (network-lookup-address-info "::1" 'ipv6 'numeric)))) | ||
| 444 | |||
| 445 | (ert-deftest lookup-hints-values () | ||
| 446 | "`network-lookup-address-info' should succeed/fail in looking up various numeric IP addresses." | ||
| 447 | (let ((ipv4-invalid-addrs | ||
| 448 | '("localhost" "343.1.2.3" "1.2.3.4.5")) | ||
| 449 | ;; These are valid for IPv4 but invalid for IPv6 | ||
| 450 | (ipv4-addrs | ||
| 451 | '("127.0.0.1" "127.0.1" "127.1" "127" "1" "0" | ||
| 452 | "0xe3010203" "0xe3.1.2.3" "227.0x1.2.3" | ||
| 453 | "034300201003" "0343.1.2.3" "227.001.2.3")) | ||
| 454 | (ipv6-only-invalid-addrs | ||
| 455 | '("fe80:1" "e301:203:1" "e301::203::1" | ||
| 456 | "1:2:3:4:5:6:7:8:9" "0xe301:203::1" | ||
| 457 | "343:10001:2::3" | ||
| 458 | ;; "00343:1:2::3" is invalid on GNU/Linux and FreeBSD, but | ||
| 459 | ;; valid on macOS. macOS is wrong here, but such is life. | ||
| 460 | )) | ||
| 461 | ;; These are valid for IPv6 but invalid for IPv4 | ||
| 462 | (ipv6-addrs | ||
| 463 | '("fe80::1" "e301::203:1" "e301:203::1" | ||
| 464 | "e301:0203::1" "::1" "::0" | ||
| 465 | "0343:1:2::3" "343:001:2::3"))) | ||
| 466 | (dolist (a ipv4-invalid-addrs) | ||
| 467 | (should-not (network-lookup-address-info a nil 'numeric)) | ||
| 468 | (should-not (network-lookup-address-info a 'ipv4 'numeric))) | ||
| 469 | (dolist (a ipv6-addrs) | ||
| 470 | (should-not (network-lookup-address-info a 'ipv4 'numeric))) | ||
| 471 | (dolist (a ipv4-addrs) | ||
| 472 | (should (network-lookup-address-info a nil 'numeric)) | ||
| 473 | (should (network-lookup-address-info a 'ipv4 'numeric))) | ||
| 474 | (when (ipv6-is-available) | ||
| 475 | (dolist (a ipv4-addrs) | ||
| 476 | (should-not (network-lookup-address-info a 'ipv6 'numeric))) | ||
| 477 | (dolist (a ipv6-only-invalid-addrs) | ||
| 478 | (should-not (network-lookup-address-info a 'ipv6 'numeric))) | ||
| 479 | (dolist (a ipv6-addrs) | ||
| 480 | (should (network-lookup-address-info a nil 'numeric)) | ||
| 481 | (should (network-lookup-address-info a 'ipv6 'numeric)) | ||
| 482 | (should (network-lookup-address-info (upcase a) nil 'numeric)) | ||
| 483 | (should (network-lookup-address-info (upcase a) 'ipv6 'numeric)))))) | ||
| 484 | |||
| 485 | (ert-deftest lookup-unicode-domains () | ||
| 486 | "Unicode domains should fail." | ||
| 487 | (skip-unless internet-is-working) | ||
| 488 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 489 | (should-error (network-lookup-address-info "faß.de")) | ||
| 490 | (should (network-lookup-address-info (puny-encode-domain "faß.de"))))) | ||
| 491 | |||
| 492 | (ert-deftest unibyte-domain-name () | ||
| 493 | "Unibyte domain names should work." | ||
| 494 | (skip-unless internet-is-working) | ||
| 495 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 496 | (should (network-lookup-address-info (string-to-unibyte "google.com"))))) | ||
| 497 | |||
| 498 | (ert-deftest lookup-google () | ||
| 499 | "Check that we can look up google IP addresses." | ||
| 500 | (skip-unless internet-is-working) | ||
| 501 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 502 | (let ((addresses-both (network-lookup-address-info "google.com")) | ||
| 503 | (addresses-v4 (network-lookup-address-info "google.com" 'ipv4))) | ||
| 504 | (should addresses-both) | ||
| 505 | (should addresses-v4)) | ||
| 506 | (when (and (ipv6-is-available) | ||
| 507 | (dns-query "google.com" 'AAAA)) | ||
| 508 | (should (network-lookup-address-info "google.com" 'ipv6))))) | ||
| 509 | |||
| 510 | (ert-deftest non-existent-lookup-failure () | ||
| 511 | "Check that looking up non-existent domain returns nil." | ||
| 512 | (skip-unless internet-is-working) | ||
| 513 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 514 | (should (eq nil (network-lookup-address-info "emacs.invalid"))))) | ||
| 515 | |||
| 516 | ;; End of tests requiring DNS | ||
| 517 | |||
| 518 | (defmacro process-tests--ignore-EMFILE (&rest body) | ||
| 519 | "Evaluate BODY, ignoring EMFILE errors." | ||
| 520 | (declare (indent 0) (debug t)) | ||
| 521 | (let ((err (make-symbol "err")) | ||
| 522 | (message (make-symbol "message"))) | ||
| 523 | `(let ((,message (process-tests--EMFILE-message))) | ||
| 524 | (condition-case ,err | ||
| 525 | ,(macroexp-progn body) | ||
| 526 | (file-error | ||
| 527 | ;; If we couldn't determine the EMFILE message, just ignore | ||
| 528 | ;; all `file-error' signals. | ||
| 529 | (and ,message | ||
| 530 | (not (string-equal (caddr ,err) ,message)) | ||
| 531 | (signal (car ,err) (cdr ,err)))))))) | ||
| 532 | |||
| 533 | (defmacro process-tests--with-buffers (var &rest body) | ||
| 534 | "Bind VAR to nil and evaluate BODY. | ||
| 535 | Afterwards, kill all buffers in the list VAR. BODY should add | ||
| 536 | some buffer objects to VAR." | ||
| 537 | (declare (indent 1) (debug (symbolp body))) | ||
| 538 | (cl-check-type var symbol) | ||
| 539 | `(let ((,var nil)) | ||
| 540 | (unwind-protect | ||
| 541 | ,(macroexp-progn body) | ||
| 542 | (mapc #'kill-buffer ,var)))) | ||
| 543 | |||
| 544 | (defmacro process-tests--with-processes (var &rest body) | ||
| 545 | "Bind VAR to nil and evaluate BODY. | ||
| 546 | Afterwards, delete all processes in the list VAR. BODY should | ||
| 547 | add some process objects to VAR." | ||
| 548 | (declare (indent 1) (debug (symbolp body))) | ||
| 549 | (cl-check-type var symbol) | ||
| 550 | `(let ((,var nil)) | ||
| 551 | (unwind-protect | ||
| 552 | ,(macroexp-progn body) | ||
| 553 | (mapc #'delete-process ,var)))) | ||
| 554 | |||
| 555 | (defmacro process-tests--with-raised-rlimit (&rest body) | ||
| 556 | "Evaluate BODY using a higher limit for the number of open files. | ||
| 557 | Attempt to set the resource limit for the number of open files | ||
| 558 | temporarily to the highest possible value." | ||
| 559 | (declare (indent 0) (debug t)) | ||
| 560 | (let ((prlimit (make-symbol "prlimit")) | ||
| 561 | (soft (make-symbol "soft")) | ||
| 562 | (hard (make-symbol "hard")) | ||
| 563 | (pid-arg (make-symbol "pid-arg"))) | ||
| 564 | `(let ((,prlimit (executable-find "prlimit")) | ||
| 565 | (,pid-arg (format "--pid=%d" (emacs-pid))) | ||
| 566 | (,soft nil) (,hard nil)) | ||
| 567 | (cl-flet ((set-limit | ||
| 568 | (value) | ||
| 569 | (cl-check-type value natnum) | ||
| 570 | (when ,prlimit | ||
| 571 | (call-process ,prlimit nil nil nil | ||
| 572 | ,pid-arg | ||
| 573 | (format "--nofile=%d:" value))))) | ||
| 574 | (when ,prlimit | ||
| 575 | (with-temp-buffer | ||
| 576 | (when (eql (call-process ,prlimit nil t nil | ||
| 577 | ,pid-arg "--nofile" | ||
| 578 | "--raw" "--noheadings" | ||
| 579 | "--output=SOFT,HARD") | ||
| 580 | 0) | ||
| 581 | (goto-char (point-min)) | ||
| 582 | (when (looking-at (rx (group (+ digit)) (+ blank) | ||
| 583 | (group (+ digit)) ?\n)) | ||
| 584 | (setq ,soft (string-to-number | ||
| 585 | (match-string-no-properties 1)) | ||
| 586 | ,hard (string-to-number | ||
| 587 | (match-string-no-properties 2)))))) | ||
| 588 | (and ,soft ,hard (< ,soft ,hard) | ||
| 589 | (set-limit ,hard))) | ||
| 590 | (unwind-protect | ||
| 591 | ,(macroexp-progn body) | ||
| 592 | (when ,soft (set-limit ,soft))))))) | ||
| 593 | |||
| 594 | (defmacro process-tests--fd-setsize-test (&rest body) | ||
| 595 | "Run BODY as a test for FD_SETSIZE overflow. | ||
| 596 | Try to generate pipe processes until we are close to the | ||
| 597 | FD_SETSIZE limit. Within BODY, only a small number of file | ||
| 598 | descriptors should still be available. Furthermore, raise the | ||
| 599 | maximum number of open files in the Emacs process above | ||
| 600 | FD_SETSIZE." | ||
| 601 | (declare (indent 0) (debug t)) | ||
| 602 | (let ((process (make-symbol "process")) | ||
| 603 | (processes (make-symbol "processes")) | ||
| 604 | (buffer (make-symbol "buffer")) | ||
| 605 | (buffers (make-symbol "buffers")) | ||
| 606 | ;; FD_SETSIZE is typically 1024 on Unix-like systems. On | ||
| 607 | ;; MS-Windows we artificially limit FD_SETSIZE to 64, see the | ||
| 608 | ;; commentary in w32proc.c. | ||
| 609 | (fd-setsize (if (eq system-type 'windows-nt) 64 1024))) | ||
| 610 | `(process-tests--with-raised-rlimit | ||
| 611 | (process-tests--with-buffers ,buffers | ||
| 612 | (process-tests--with-processes ,processes | ||
| 613 | ;; First, allocate enough pipes to definitely exceed the | ||
| 614 | ;; FD_SETSIZE limit. | ||
| 615 | (cl-loop for i from 1 to ,(1+ fd-setsize) | ||
| 616 | for ,buffer = (generate-new-buffer | ||
| 617 | (format " *pipe %d*" i)) | ||
| 618 | do (push ,buffer ,buffers) | ||
| 619 | for ,process = (process-tests--ignore-EMFILE | ||
| 620 | (make-pipe-process | ||
| 621 | :name (format "pipe %d" i) | ||
| 622 | ;; Prevent delete-process from | ||
| 623 | ;; trying to read from pipe | ||
| 624 | ;; processes that didn't exit | ||
| 625 | ;; yet, because no one is | ||
| 626 | ;; writing to those pipes, and | ||
| 627 | ;; the read will stall. | ||
| 628 | :stop (eq system-type 'windows-nt) | ||
| 629 | :buffer ,buffer | ||
| 630 | :coding 'no-conversion | ||
| 631 | :noquery t)) | ||
| 632 | while ,process | ||
| 633 | do (push ,process ,processes)) | ||
| 634 | (unless (cddr ,processes) | ||
| 635 | (ert-fail "Couldn't allocate enough pipes")) | ||
| 636 | ;; Delete two pipes to test more edge cases. | ||
| 637 | (delete-process (pop ,processes)) | ||
| 638 | (delete-process (pop ,processes)) | ||
| 639 | ,@body))))) | ||
| 640 | |||
| 641 | ;; Tests for FD_SETSIZE overflow (Bug#24325). The following tests | ||
| 642 | ;; generate lots of process objects of the various kinds. Running the | ||
| 643 | ;; tests with assertions enabled should not result in any crashes due | ||
| 644 | ;; to file descriptor set overflow. These tests first generate lots | ||
| 645 | ;; of unused pipe processes to fill up the file descriptor space. | ||
| 646 | ;; Then, they create a few instances of the process type under test. | ||
| 647 | |||
| 648 | (ert-deftest process-tests/fd-setsize-no-crash/make-process () | ||
| 649 | "Check that Emacs doesn't crash when trying to use more than | ||
| 650 | FD_SETSIZE file descriptors (Bug#24325)." | ||
| 651 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 652 | (let ((cat (executable-find "cat"))) | ||
| 653 | (skip-unless cat) | ||
| 654 | (dolist (conn-type '(pipe pty)) | ||
| 655 | (ert-info ((format "Connection type `%s'" conn-type)) | ||
| 656 | (process-tests--fd-setsize-test | ||
| 657 | (process-tests--with-processes processes | ||
| 658 | ;; Start processes until we exhaust the file descriptor | ||
| 659 | ;; set size. We assume that each process requires at | ||
| 660 | ;; least one file descriptor. | ||
| 661 | (dotimes (i 10) | ||
| 662 | (let ((process | ||
| 663 | ;; Failure to allocate more file descriptors | ||
| 664 | ;; should signal `file-error', but not crash. | ||
| 665 | ;; Since we don't know the exact limit, we | ||
| 666 | ;; ignore `file-error'. | ||
| 667 | (process-tests--ignore-EMFILE | ||
| 668 | (make-process :name (format "test %d" i) | ||
| 669 | :command (list cat) | ||
| 670 | :connection-type conn-type | ||
| 671 | :coding 'no-conversion | ||
| 672 | :noquery t)))) | ||
| 673 | (when process (push process processes)))) | ||
| 674 | ;; We should have managed to start at least one process. | ||
| 675 | (should processes) | ||
| 676 | (dolist (process processes) | ||
| 677 | ;; The process now should either be running, or have | ||
| 678 | ;; already failed before `exec'. | ||
| 679 | (should (memq (process-status process) '(run exit))) | ||
| 680 | (when (process-live-p process) | ||
| 681 | (process-send-eof process)) | ||
| 682 | (while (accept-process-output process)) | ||
| 683 | (should (eq (process-status process) 'exit)) | ||
| 684 | ;; If there's an error between fork and exec, Emacs | ||
| 685 | ;; will use exit statuses between 125 and 127, see | ||
| 686 | ;; process.h. This can happen if the child process | ||
| 687 | ;; tries to set up terminal device but fails due to | ||
| 688 | ;; file number limits. We don't treat this as an | ||
| 689 | ;; error. | ||
| 690 | (should (memql (process-exit-status process) | ||
| 691 | '(0 125 126 127))))))))))) | ||
| 692 | |||
| 693 | (ert-deftest process-tests/fd-setsize-no-crash/make-pipe-process () | ||
| 694 | "Check that Emacs doesn't crash when trying to use more than | ||
| 695 | FD_SETSIZE file descriptors (Bug#24325)." | ||
| 696 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 697 | (process-tests--fd-setsize-test | ||
| 698 | (process-tests--with-buffers buffers | ||
| 699 | (process-tests--with-processes processes | ||
| 700 | ;; Start processes until we exhaust the file descriptor set | ||
| 701 | ;; size. We assume that each process requires at least one | ||
| 702 | ;; file descriptor. | ||
| 703 | (dotimes (i 10) | ||
| 704 | (let ((buffer (generate-new-buffer (format " *%d*" i)))) | ||
| 705 | (push buffer buffers) | ||
| 706 | (let ((process | ||
| 707 | ;; Failure to allocate more file descriptors | ||
| 708 | ;; should signal `file-error', but not crash. | ||
| 709 | ;; Since we don't know the exact limit, we ignore | ||
| 710 | ;; `file-error'. | ||
| 711 | (process-tests--ignore-EMFILE | ||
| 712 | (make-pipe-process :name (format "test %d" i) | ||
| 713 | :buffer buffer | ||
| 714 | :coding 'no-conversion | ||
| 715 | :noquery t)))) | ||
| 716 | (when process (push process processes))))) | ||
| 717 | ;; We should have managed to start at least one process. | ||
| 718 | (should processes)))))) | ||
| 719 | |||
| 720 | (ert-deftest process-tests/fd-setsize-no-crash/make-network-process () | ||
| 721 | "Check that Emacs doesn't crash when trying to use more than | ||
| 722 | FD_SETSIZE file descriptors (Bug#24325)." | ||
| 723 | (skip-unless (featurep 'make-network-process '(:server t))) | ||
| 724 | (skip-unless (featurep 'make-network-process '(:family local))) | ||
| 725 | ;; Avoid hang due to connect/accept handshake on Cygwin (bug#49496). | ||
| 726 | (skip-unless (not (eq system-type 'cygwin))) | ||
| 727 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 728 | (ert-with-temp-directory directory | ||
| 729 | (process-tests--with-processes processes | ||
| 730 | (let* ((num-clients 10) | ||
| 731 | (socket-name (expand-file-name "socket" directory)) | ||
| 732 | ;; Run a UNIX server to connect to. | ||
| 733 | (server (make-network-process :name "server" | ||
| 734 | :server num-clients | ||
| 735 | :buffer nil | ||
| 736 | :service socket-name | ||
| 737 | :family 'local | ||
| 738 | :coding 'no-conversion | ||
| 739 | :noquery t))) | ||
| 740 | (push server processes) | ||
| 741 | (process-tests--fd-setsize-test | ||
| 742 | ;; Start processes until we exhaust the file descriptor | ||
| 743 | ;; set size. We assume that each process requires at | ||
| 744 | ;; least one file descriptor. | ||
| 745 | (dotimes (i num-clients) | ||
| 746 | (let ((client | ||
| 747 | ;; Failure to allocate more file descriptors | ||
| 748 | ;; should signal `file-error', but not crash. | ||
| 749 | ;; Since we don't know the exact limit, we ignore | ||
| 750 | ;; `file-error'. | ||
| 751 | (process-tests--ignore-EMFILE | ||
| 752 | (make-network-process | ||
| 753 | :name (format "client %d" i) | ||
| 754 | :service socket-name | ||
| 755 | :family 'local | ||
| 756 | :coding 'no-conversion | ||
| 757 | :noquery t)))) | ||
| 758 | (when client (push client processes)))) | ||
| 759 | ;; We should have managed to start at least one process. | ||
| 760 | (should processes))))))) | ||
| 761 | |||
| 762 | (ert-deftest process-tests/fd-setsize-no-crash/make-serial-process () | ||
| 763 | "Check that Emacs doesn't crash when trying to use more than | ||
| 764 | FD_SETSIZE file descriptors (Bug#24325)." | ||
| 765 | ;; This test cannot be run if PTYs aren't supported. | ||
| 766 | (skip-unless (not (eq system-type 'windows-nt))) | ||
| 767 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 768 | (process-tests--with-processes processes | ||
| 769 | ;; In order to use `make-serial-process', we need to create some | ||
| 770 | ;; pseudoterminals. The easiest way to do that is to start a | ||
| 771 | ;; normal process using the `pty' connection type. We need to | ||
| 772 | ;; ensure that the terminal stays around while we connect to it. | ||
| 773 | ;; Create the host processes before the dummy pipes so we have a | ||
| 774 | ;; high chance of succeeding here. | ||
| 775 | (let ((sleep (executable-find "sleep")) | ||
| 776 | (tty-names ())) | ||
| 777 | (skip-unless sleep) | ||
| 778 | (dotimes (i 10) | ||
| 779 | (let* ((host (make-process :name (format "tty host %d" i) | ||
| 780 | :command (list sleep "60") | ||
| 781 | :buffer nil | ||
| 782 | :coding 'utf-8-unix | ||
| 783 | :connection-type 'pty | ||
| 784 | :noquery t)) | ||
| 785 | (tty-name (process-tty-name host))) | ||
| 786 | (should (processp host)) | ||
| 787 | (push host processes) | ||
| 788 | ;; FIXME: The assumption below that using :connection 'pty | ||
| 789 | ;; in make-process necessarily produces a process with PTY | ||
| 790 | ;; connection is unreliable and non-portable. | ||
| 791 | ;; make-process can legitimately and silently fall back on | ||
| 792 | ;; pipes if allocating a PTY fails (and on MS-Windows it | ||
| 793 | ;; always fails). The following code also assumes that | ||
| 794 | ;; process-tty-name produces a file name that can be | ||
| 795 | ;; passed to 'stat' and to make-serial-process, which is | ||
| 796 | ;; also non-portable. | ||
| 797 | (should tty-name) | ||
| 798 | (should (file-exists-p tty-name)) | ||
| 799 | (should-not (member tty-name tty-names)) | ||
| 800 | (push tty-name tty-names))) | ||
| 801 | (process-tests--fd-setsize-test | ||
| 802 | (process-tests--with-processes processes | ||
| 803 | (process-tests--with-buffers buffers | ||
| 804 | (dolist (tty-name tty-names) | ||
| 805 | (let ((buffer (generate-new-buffer | ||
| 806 | (format " *%s*" tty-name)))) | ||
| 807 | (push buffer buffers) | ||
| 808 | ;; Failure to allocate more file descriptors should | ||
| 809 | ;; signal `file-error', but not crash. Since we | ||
| 810 | ;; don't know the exact limit, we ignore | ||
| 811 | ;; `file-error'. | ||
| 812 | (let ((process (process-tests--ignore-EMFILE | ||
| 813 | (make-serial-process | ||
| 814 | :name (format "test %s" tty-name) | ||
| 815 | :port tty-name | ||
| 816 | :speed 9600 | ||
| 817 | :buffer buffer | ||
| 818 | :coding 'no-conversion | ||
| 819 | :noquery t)))) | ||
| 820 | (when process (push process processes)))))) | ||
| 821 | ;; We should have managed to start at least one process. | ||
| 822 | (should processes))))))) | ||
| 823 | |||
| 824 | (defvar process-tests--EMFILE-message :unknown | ||
| 825 | "Cached result of the function `process-tests--EMFILE-message'.") | ||
| 826 | |||
| 827 | (defun process-tests--EMFILE-message () | ||
| 828 | "Return the error message for the EMFILE POSIX error. | ||
| 829 | Return nil if that can't be determined." | ||
| 830 | (when (eq process-tests--EMFILE-message :unknown) | ||
| 831 | (setq process-tests--EMFILE-message | ||
| 832 | (with-temp-buffer | ||
| 833 | (when (eql (ignore-error 'file-error | ||
| 834 | (call-process "errno" nil t nil "EMFILE")) | ||
| 835 | 0) | ||
| 836 | (goto-char (point-min)) | ||
| 837 | (when (looking-at (rx "EMFILE" (+ blank) (+ digit) | ||
| 838 | (+ blank) (group (+ nonl)))) | ||
| 839 | (match-string-no-properties 1)))))) | ||
| 840 | process-tests--EMFILE-message) | ||
| 841 | |||
| 842 | (ert-deftest process-tests/sentinel-called () | ||
| 843 | "Check that sentinels are called after processes finish." | ||
| 844 | (let ((command (process-tests--emacs-command))) | ||
| 845 | (skip-unless command) | ||
| 846 | (dolist (conn-type '(pipe pty)) | ||
| 847 | (ert-info ((format "Connection type: %s" conn-type)) | ||
| 848 | (process-tests--with-processes processes | ||
| 849 | (let* ((calls ()) | ||
| 850 | (process (make-process | ||
| 851 | :name "echo" | ||
| 852 | :command (process-tests--eval | ||
| 853 | command '(print "first")) | ||
| 854 | :noquery t | ||
| 855 | :connection-type conn-type | ||
| 856 | :coding 'utf-8-unix | ||
| 857 | :sentinel (lambda (process message) | ||
| 858 | (push (list process message) | ||
| 859 | calls))))) | ||
| 860 | (push process processes) | ||
| 861 | (while (accept-process-output process)) | ||
| 862 | (should (equal calls | ||
| 863 | (list (list process "finished\n")))))))))) | ||
| 864 | |||
| 865 | (ert-deftest process-tests/sentinel-with-multiple-processes () | ||
| 866 | "Check that sentinels are called in time even when other processes | ||
| 867 | have written output." | ||
| 868 | (let ((command (process-tests--emacs-command))) | ||
| 869 | (skip-unless command) | ||
| 870 | (dolist (conn-type '(pipe pty)) | ||
| 871 | (ert-info ((format "Connection type: %s" conn-type)) | ||
| 872 | (process-tests--with-processes processes | ||
| 873 | (let* ((calls ()) | ||
| 874 | (process (make-process | ||
| 875 | :name "echo" | ||
| 876 | :command (process-tests--eval | ||
| 877 | command '(print "first")) | ||
| 878 | :noquery t | ||
| 879 | :connection-type conn-type | ||
| 880 | :coding 'utf-8-unix | ||
| 881 | :sentinel (lambda (process message) | ||
| 882 | (push (list process message) | ||
| 883 | calls))))) | ||
| 884 | (push process processes) | ||
| 885 | (push (make-process | ||
| 886 | :name "bash" | ||
| 887 | :command (process-tests--eval | ||
| 888 | command | ||
| 889 | '(progn (sleep-for 10) (print "second"))) | ||
| 890 | :noquery t | ||
| 891 | :connection-type conn-type) | ||
| 892 | processes) | ||
| 893 | (while (accept-process-output process)) | ||
| 894 | (should (equal calls | ||
| 895 | (list (list process "finished\n")))))))))) | ||
| 896 | |||
| 897 | (ert-deftest process-tests/multiple-threads-waiting () | ||
| 898 | :tags (if (getenv "EMACS_EMBA_CI") '(:unstable)) | ||
| 899 | (skip-unless (fboundp 'make-thread)) | ||
| 900 | (with-timeout (60 (ert-fail "Test timed out")) | ||
| 901 | (process-tests--with-processes processes | ||
| 902 | (let ((threads ()) | ||
| 903 | (cat (executable-find "cat"))) | ||
| 904 | (skip-unless cat) | ||
| 905 | (dotimes (i 10) | ||
| 906 | (let* ((name (format "test %d" i)) | ||
| 907 | (process (make-process :name name | ||
| 908 | :command (list cat) | ||
| 909 | :coding 'no-conversion | ||
| 910 | :noquery t | ||
| 911 | :connection-type 'pipe))) | ||
| 912 | (push process processes) | ||
| 913 | (set-process-thread process nil) | ||
| 914 | (push (make-thread | ||
| 915 | (lambda () | ||
| 916 | (while (accept-process-output process))) | ||
| 917 | name) | ||
| 918 | threads))) | ||
| 919 | (mapc #'process-send-eof processes) | ||
| 920 | (cl-loop for process in processes | ||
| 921 | and thread in threads | ||
| 922 | do | ||
| 923 | (should-not (thread-join thread)) | ||
| 924 | (should-not (thread-last-error)) | ||
| 925 | (should (eq (process-status process) 'exit)) | ||
| 926 | (should (eql (process-exit-status process) 0))))))) | ||
| 927 | |||
| 928 | (defun process-tests--eval (command form) | ||
| 929 | "Return a command that evaluates FORM in an Emacs subprocess. | ||
| 930 | COMMAND must be a list returned by | ||
| 931 | `process-tests--emacs-command'." | ||
| 932 | (let ((print-gensym t) | ||
| 933 | (print-circle t) | ||
| 934 | (print-length nil) | ||
| 935 | (print-level nil) | ||
| 936 | (print-escape-control-characters t) | ||
| 937 | (print-escape-newlines t) | ||
| 938 | (print-escape-multibyte t) | ||
| 939 | (print-escape-nonascii t)) | ||
| 940 | `(,@command "--quick" "--batch" ,(format "--eval=%S" form)))) | ||
| 941 | |||
| 942 | (defun process-tests--emacs-command () | ||
| 943 | "Return a command to reinvoke the current Emacs instance. | ||
| 944 | Return nil if that doesn't appear to be possible." | ||
| 945 | (when-let ((binary (process-tests--emacs-binary)) | ||
| 946 | (dump (process-tests--dump-file))) | ||
| 947 | (cons binary | ||
| 948 | (unless (eq dump :not-needed) | ||
| 949 | (list (concat "--dump-file=" | ||
| 950 | (file-name-unquote dump))))))) | ||
| 951 | |||
| 952 | (defun process-tests--emacs-binary () | ||
| 953 | "Return the filename of the currently running Emacs binary. | ||
| 954 | Return nil if that can't be determined." | ||
| 955 | (and (stringp invocation-name) | ||
| 956 | (not (file-remote-p invocation-name)) | ||
| 957 | (not (file-name-absolute-p invocation-name)) | ||
| 958 | (stringp invocation-directory) | ||
| 959 | (not (file-remote-p invocation-directory)) | ||
| 960 | (file-name-absolute-p invocation-directory) | ||
| 961 | (when-let ((file (process-tests--usable-file-for-reinvoke | ||
| 962 | (expand-file-name invocation-name | ||
| 963 | invocation-directory)))) | ||
| 964 | (and (file-executable-p file) file)))) | ||
| 965 | |||
| 966 | (defun process-tests--dump-file () | ||
| 967 | "Return the filename of the dump file used to start Emacs. | ||
| 968 | Return nil if that can't be determined. Return `:not-needed' if | ||
| 969 | Emacs wasn't started with a dump file." | ||
| 970 | (if-let ((stats (and (fboundp 'pdumper-stats) (pdumper-stats)))) | ||
| 971 | (when-let ((file (process-tests--usable-file-for-reinvoke | ||
| 972 | (cdr (assq 'dump-file-name stats))))) | ||
| 973 | (and (file-readable-p file) file)) | ||
| 974 | :not-needed)) | ||
| 975 | |||
| 976 | (defun process-tests--usable-file-for-reinvoke (filename) | ||
| 977 | "Return a version of FILENAME that can be used to reinvoke Emacs. | ||
| 978 | Return nil if FILENAME doesn't exist." | ||
| 979 | (when (and (stringp filename) | ||
| 980 | (not (file-remote-p filename))) | ||
| 981 | (cl-callf file-truename filename) | ||
| 982 | (and (stringp filename) | ||
| 983 | (not (file-remote-p filename)) | ||
| 984 | (file-name-absolute-p filename) | ||
| 985 | (file-regular-p filename) | ||
| 986 | filename))) | ||
| 987 | |||
| 988 | ;; Bug#46284 | ||
| 989 | (ert-deftest process-sentinel-interrupt-event () | ||
| 990 | "Test that interrupting a process on Windows sends \"interrupt\" to sentinel." | ||
| 991 | (skip-unless (eq system-type 'windows-nt)) | ||
| 992 | (with-temp-buffer | ||
| 993 | (let* ((proc-buf (current-buffer)) | ||
| 994 | ;; Start a new emacs process to wait idly until interrupted. | ||
| 995 | (cmd "emacs -batch --eval=\"(sit-for 50000)\"") | ||
| 996 | (proc (start-file-process-shell-command | ||
| 997 | "test/process-sentinel-signal-event" proc-buf cmd)) | ||
| 998 | (events '())) | ||
| 999 | |||
| 1000 | ;; Capture any incoming events. | ||
| 1001 | (set-process-sentinel proc | ||
| 1002 | (lambda (_prc event) | ||
| 1003 | (push event events))) | ||
| 1004 | ;; Wait for the process to start. | ||
| 1005 | (sleep-for 2) | ||
| 1006 | (should (equal 'run (process-status proc))) | ||
| 1007 | ;; Interrupt the sub-process and wait for it to die. | ||
| 1008 | (interrupt-process proc) | ||
| 1009 | (sleep-for 2) | ||
| 1010 | ;; Should have received SIGINT... | ||
| 1011 | (should (equal 'signal (process-status proc))) | ||
| 1012 | (should (equal 2 (process-exit-status proc))) | ||
| 1013 | ;; ...and the change description should be "interrupt". | ||
| 1014 | (should (equal '("interrupt\n") events))))) | ||
| 1015 | |||
| 1016 | (ert-deftest process-num-processors () | ||
| 1017 | "Sanity checks for num-processors." | ||
| 1018 | (should (equal (num-processors) (num-processors))) | ||
| 1019 | (should (integerp (num-processors))) | ||
| 1020 | (should (< 0 (num-processors)))) | ||
| 164 | 1021 | ||
| 165 | (provide 'process-tests) | 1022 | (provide 'process-tests) |
| 166 | ;; process-tests.el ends here. | 1023 | ;;; process-tests.el ends here |