aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Porter2022-07-17 20:25:00 -0700
committerJim Porter2022-08-05 17:58:54 -0700
commitd7b89ea4077d4fe677ba0577245328819ee79cdc (patch)
treed4e499042bdf2f301be7f2d7ec05f0d1bfd8d44b
parentb70369c557efed3dcd86dc64a2e73e3480dea6af (diff)
downloademacs-d7b89ea4077d4fe677ba0577245328819ee79cdc.tar.gz
emacs-d7b89ea4077d4fe677ba0577245328819ee79cdc.zip
Allow creating processes where only one of stdin or stdout is a PTY
* src/lisp.h (emacs_spawn): * src/callproc.c (emacs_spawn): Add PTY_IN and PTY_OUT arguments to specify which streams should be set up as a PTY. (call_process): Adjust call to 'emacs_spawn'. * src/process.h (Lisp_Process): Replace 'pty_flag' with 'pty_in' and 'pty_out'. * src/process.c (is_pty_from_symbol): New function. (make-process): Allow :connection-type to be a cons cell, and allow using a stderr process with a PTY for stdin/stdout. (create_process): Handle creating a process where only one of stdin or stdout is a PTY. * lisp/eshell/esh-proc.el (eshell-needs-pipe, eshell-needs-pipe-p): Remove. (eshell-gather-process-output): Use 'make-process' and set ':connection-type' as needed by the value of 'eshell-in-pipeline-p'. * lisp/net/tramp.el (tramp-handle-make-process): * lisp/net/tramp-adb.el (tramp-adb-handle-make-process): * lisp/net/tramp-sh.el (tramp-sh-handle-make-process): Don't signal an error when ':connection-type' is a cons cell. * test/src/process-tests.el (process-test-sentinel-wait-function-working-p): Allow passing PROC in, and rework into... (process-test-wait-for-sentinel): ... this. (process-test-sentinel-accept-process-output) (process-test-sentinel-sit-for, process-test-quoted-batfile) (process-test-stderr-filter): Use 'process-test-wait-for-sentinel'. (make/process/test-connection-type): New function. (make-process/connection-type/pty, make-process/connection-type/pty-2) (make-process/connection-type/pipe) (make-process/connection-type/pipe-2) (make-process/connection-type/in-pty) (make-process/connection-type/out-pty) (make-process/connection-type/pty-with-stderr-buffer) (make-process/connection-type/out-pty-with-stderr-buffer): New tests. * test/lisp/eshell/esh-proc-tests.el (esh-proc-test--detect-pty-cmd): New variable. (esh-proc-test/pipeline-connection-type/no-pipeline) (esh-proc-test/pipeline-connection-type/first) (esh-proc-test/pipeline-connection-type/middle) (esh-proc-test/pipeline-connection-type/last): New tests. * doc/lispref/processes.texi (Asynchronous Processes): Document new ':connection-type' behavior. (Output from Processes): Remove caveat about ':stderr' forcing 'make-process' to use pipes. * etc/NEWS: Announce this change (bug#56025).
-rw-r--r--doc/lispref/processes.texi28
-rw-r--r--etc/NEWS12
-rw-r--r--lisp/eshell/esh-proc.el55
-rw-r--r--lisp/net/tramp-adb.el5
-rw-r--r--lisp/net/tramp-sh.el5
-rw-r--r--lisp/net/tramp.el5
-rw-r--r--src/callproc.c37
-rw-r--r--src/lisp.h3
-rw-r--r--src/process.c129
-rw-r--r--src/process.h5
-rw-r--r--test/lisp/eshell/esh-proc-tests.el43
-rw-r--r--test/src/process-tests.el121
12 files changed, 288 insertions, 160 deletions
diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index 1ef8fc3d03a..e253ab9de03 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -705,12 +705,13 @@ coding system will apply. @xref{Default Coding Systems}.
705Initialize the type of device used to communicate with the subprocess. 705Initialize the type of device used to communicate with the subprocess.
706Possible values are @code{pty} to use a pty, @code{pipe} to use a 706Possible values are @code{pty} to use a pty, @code{pipe} to use a
707pipe, or @code{nil} to use the default derived from the value of the 707pipe, or @code{nil} to use the default derived from the value of the
708@code{process-connection-type} variable. This parameter and the value 708@code{process-connection-type} variable. If @var{type} is a cons cell
709of @code{process-connection-type} are ignored if a non-@code{nil} 709@w{@code{(@var{input} . @var{output})}}, then @var{input} will be used
710value is specified for the @code{:stderr} parameter; in that case, the 710for standard input and @var{output} for standard output (and standard
711type will always be @code{pipe}. On systems where ptys are not 711error if @code{:stderr} is @code{nil}).
712available (MS-Windows), this parameter is likewise ignored, and pipes 712
713are used unconditionally. 713On systems where ptys are not available (MS-Windows), this parameter
714is ignored, and pipes are used unconditionally.
714 715
715@item :noquery @var{query-flag} 716@item :noquery @var{query-flag}
716Initialize the process query flag to @var{query-flag}. 717Initialize the process query flag to @var{query-flag}.
@@ -1530,20 +1531,11 @@ a buffer, which is called the associated buffer of the process
1530default filter discards the output. 1531default filter discards the output.
1531 1532
1532 If the subprocess writes to its standard error stream, by default 1533 If the subprocess writes to its standard error stream, by default
1533the error output is also passed to the process filter function. If 1534the error output is also passed to the process filter function.
1534Emacs uses a pseudo-TTY (pty) for communication with the subprocess, 1535Alternatively, you could use the @code{:stderr} parameter with a
1535then it is impossible to separate the standard output and standard
1536error streams of the subprocess, because a pseudo-TTY has only one
1537output channel. In that case, if you want to keep the output to those
1538streams separate, you should redirect one of them to a file---for
1539example, by using an appropriate shell command via
1540@code{start-process-shell-command} or a similar function.
1541
1542 Alternatively, you could use the @code{:stderr} parameter with a
1543non-@code{nil} value in a call to @code{make-process} 1536non-@code{nil} value in a call to @code{make-process}
1544(@pxref{Asynchronous Processes, make-process}) to make the destination 1537(@pxref{Asynchronous Processes, make-process}) to make the destination
1545of the error output separate from the standard output; in that case, 1538of the error output separate from the standard output.
1546Emacs will use pipes for communicating with the subprocess.
1547 1539
1548 When a subprocess terminates, Emacs reads any pending output, 1540 When a subprocess terminates, Emacs reads any pending output,
1549then stops reading output from that subprocess. Therefore, if the 1541then stops reading output from that subprocess. Therefore, if the
diff --git a/etc/NEWS b/etc/NEWS
index dc8bd6ce24b..8a9744ab3e2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2332,6 +2332,12 @@ they will still be escaped, so the '.foo' symbol is still printed as
2332and remapping parent of basic faces does not work reliably. 2332and remapping parent of basic faces does not work reliably.
2333Instead of remapping 'mode-line', you have to remap 'mode-line-active'. 2333Instead of remapping 'mode-line', you have to remap 'mode-line-active'.
2334 2334
2335+++
2336** 'make-process' has been extended to support ptys when ':stderr' is set.
2337Previously, setting ':stderr' to a non-nil value would force the
2338process's connection to use pipes. Now, Emacs will use a pty for
2339stdin and stdout if requested no matter the value of ':stderr'.
2340
2335--- 2341---
2336** User option 'mail-source-ignore-errors' is now obsolete. 2342** User option 'mail-source-ignore-errors' is now obsolete.
2337The whole mechanism for prompting users to continue in case of 2343The whole mechanism for prompting users to continue in case of
@@ -3324,6 +3330,12 @@ This is useful when quoting shell arguments for a remote shell
3324invocation. Such shells are POSIX conformant by default. 3330invocation. Such shells are POSIX conformant by default.
3325 3331
3326+++ 3332+++
3333** 'make-process' can set connection type independently for input and output.
3334When calling 'make-process', communication via pty can be enabled
3335selectively for just input or output by passing a cons cell for
3336':connection-type', e.g. '(pipe . pty)'.
3337
3338+++
3327** 'signal-process' now consults the list 'signal-process-functions'. 3339** 'signal-process' now consults the list 'signal-process-functions'.
3328This is to determine which function has to be called in order to 3340This is to determine which function has to be called in order to
3329deliver the signal. This allows Tramp to send the signal to remote 3341deliver the signal. This allows Tramp to send the signal to remote
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 70426ccaf2a..99b43661f2c 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -250,30 +250,6 @@ The prompt will be set to PROMPT."
250 "A marker that tracks the beginning of output of the last subprocess. 250 "A marker that tracks the beginning of output of the last subprocess.
251Used only on systems which do not support async subprocesses.") 251Used only on systems which do not support async subprocesses.")
252 252
253(defvar eshell-needs-pipe
254 '("bc"
255 ;; xclip.el (in GNU ELPA) calls all of these with
256 ;; `process-connection-type' set to nil.
257 "pbpaste" "putclip" "xclip" "xsel" "wl-copy")
258 "List of commands which need `process-connection-type' to be nil.
259Currently only affects commands in pipelines, and not those at
260the front. If an element contains a directory part it must match
261the full name of a command, otherwise just the nondirectory part must match.")
262
263(defun eshell-needs-pipe-p (command)
264 "Return non-nil if COMMAND needs `process-connection-type' to be nil.
265See `eshell-needs-pipe'."
266 (and (bound-and-true-p eshell-in-pipeline-p)
267 (not (eq eshell-in-pipeline-p 'first))
268 ;; FIXME should this return non-nil for anything that is
269 ;; neither 'first nor 'last? See bug#1388 discussion.
270 (catch 'found
271 (dolist (exe eshell-needs-pipe)
272 (if (string-equal exe (if (string-search "/" exe)
273 command
274 (file-name-nondirectory command)))
275 (throw 'found t))))))
276
277(defun eshell-gather-process-output (command args) 253(defun eshell-gather-process-output (command args)
278 "Gather the output from COMMAND + ARGS." 254 "Gather the output from COMMAND + ARGS."
279 (require 'esh-var) 255 (require 'esh-var)
@@ -290,31 +266,36 @@ See `eshell-needs-pipe'."
290 (cond 266 (cond
291 ((fboundp 'make-process) 267 ((fboundp 'make-process)
292 (setq proc 268 (setq proc
293 (let ((process-connection-type 269 (let ((command (file-local-name (expand-file-name command)))
294 (unless (eshell-needs-pipe-p command) 270 (conn-type (pcase (bound-and-true-p eshell-in-pipeline-p)
295 process-connection-type)) 271 ('first '(nil . pipe))
296 (command (file-local-name (expand-file-name command)))) 272 ('last '(pipe . nil))
297 (apply #'start-file-process 273 ('t 'pipe)
298 (file-name-nondirectory command) nil command args))) 274 ('nil nil))))
275 (make-process
276 :name (file-name-nondirectory command)
277 :buffer (current-buffer)
278 :command (cons command args)
279 :filter (if (eshell-interactive-output-p)
280 #'eshell-output-filter
281 #'eshell-insertion-filter)
282 :sentinel #'eshell-sentinel
283 :connection-type conn-type
284 :file-handler t)))
299 (eshell-record-process-object proc) 285 (eshell-record-process-object proc)
300 (set-process-buffer proc (current-buffer))
301 (set-process-filter proc (if (eshell-interactive-output-p)
302 #'eshell-output-filter
303 #'eshell-insertion-filter))
304 (set-process-sentinel proc #'eshell-sentinel)
305 (run-hook-with-args 'eshell-exec-hook proc) 286 (run-hook-with-args 'eshell-exec-hook proc)
306 (when (fboundp 'process-coding-system) 287 (when (fboundp 'process-coding-system)
307 (let ((coding-systems (process-coding-system proc))) 288 (let ((coding-systems (process-coding-system proc)))
308 (setq decoding (car coding-systems) 289 (setq decoding (car coding-systems)
309 encoding (cdr coding-systems))) 290 encoding (cdr coding-systems)))
310 ;; If start-process decided to use some coding system for 291 ;; If `make-process' decided to use some coding system for
311 ;; decoding data sent from the process and the coding system 292 ;; decoding data sent from the process and the coding system
312 ;; doesn't specify EOL conversion, we had better convert CRLF 293 ;; doesn't specify EOL conversion, we had better convert CRLF
313 ;; to LF. 294 ;; to LF.
314 (if (vectorp (coding-system-eol-type decoding)) 295 (if (vectorp (coding-system-eol-type decoding))
315 (setq decoding (coding-system-change-eol-conversion decoding 'dos) 296 (setq decoding (coding-system-change-eol-conversion decoding 'dos)
316 changed t)) 297 changed t))
317 ;; Even if start-process left the coding system for encoding 298 ;; Even if `make-process' left the coding system for encoding
318 ;; data sent from the process undecided, we had better use the 299 ;; data sent from the process undecided, we had better use the
319 ;; same one as what we use for decoding. But, we should 300 ;; same one as what we use for decoding. But, we should
320 ;; suppress EOL conversion. 301 ;; suppress EOL conversion.
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index ef0cc2d66c6..918de68ea9b 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -877,7 +877,10 @@ implementation will be used."
877 (signal 'wrong-type-argument (list #'symbolp coding))) 877 (signal 'wrong-type-argument (list #'symbolp coding)))
878 (when (eq connection-type t) 878 (when (eq connection-type t)
879 (setq connection-type 'pty)) 879 (setq connection-type 'pty))
880 (unless (memq connection-type '(nil pipe pty)) 880 (unless (or (and (consp connection-type)
881 (memq (car connection-type) '(nil pipe pty))
882 (memq (cdr connection-type) '(nil pipe pty)))
883 (memq connection-type '(nil pipe pty)))
881 (signal 'wrong-type-argument (list #'symbolp connection-type))) 884 (signal 'wrong-type-argument (list #'symbolp connection-type)))
882 (unless (or (null filter) (eq filter t) (functionp filter)) 885 (unless (or (null filter) (eq filter t) (functionp filter))
883 (signal 'wrong-type-argument (list #'functionp filter))) 886 (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index 9e5347252ad..38fffadd4ec 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -2842,7 +2842,10 @@ implementation will be used."
2842 (signal 'wrong-type-argument (list #'symbolp coding))) 2842 (signal 'wrong-type-argument (list #'symbolp coding)))
2843 (when (eq connection-type t) 2843 (when (eq connection-type t)
2844 (setq connection-type 'pty)) 2844 (setq connection-type 'pty))
2845 (unless (memq connection-type '(nil pipe pty)) 2845 (unless (or (and (consp connection-type)
2846 (memq (car connection-type) '(nil pipe pty))
2847 (memq (cdr connection-type) '(nil pipe pty)))
2848 (memq connection-type '(nil pipe pty)))
2846 (signal 'wrong-type-argument (list #'symbolp connection-type))) 2849 (signal 'wrong-type-argument (list #'symbolp connection-type)))
2847 (unless (or (null filter) (eq filter t) (functionp filter)) 2850 (unless (or (null filter) (eq filter t) (functionp filter))
2848 (signal 'wrong-type-argument (list #'functionp filter))) 2851 (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index dcc8c632f91..ae31287eced 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -4708,7 +4708,10 @@ substitution. SPEC-LIST is a list of char/value pairs used for
4708 (signal 'wrong-type-argument (list #'symbolp coding))) 4708 (signal 'wrong-type-argument (list #'symbolp coding)))
4709 (when (eq connection-type t) 4709 (when (eq connection-type t)
4710 (setq connection-type 'pty)) 4710 (setq connection-type 'pty))
4711 (unless (memq connection-type '(nil pipe pty)) 4711 (unless (or (and (consp connection-type)
4712 (memq (car connection-type) '(nil pipe pty))
4713 (memq (cdr connection-type) '(nil pipe pty)))
4714 (memq connection-type '(nil pipe pty)))
4712 (signal 'wrong-type-argument (list #'symbolp connection-type))) 4715 (signal 'wrong-type-argument (list #'symbolp connection-type)))
4713 (unless (or (null filter) (eq filter t) (functionp filter)) 4716 (unless (or (null filter) (eq filter t) (functionp filter))
4714 (signal 'wrong-type-argument (list #'functionp filter))) 4717 (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/src/callproc.c b/src/callproc.c
index dd162f36a6c..aec0a2f5a58 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -650,7 +650,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd,
650 650
651 child_errno 651 child_errno
652 = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env, 652 = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env,
653 SSDATA (current_dir), NULL, &oldset); 653 SSDATA (current_dir), NULL, false, false, &oldset);
654 eassert ((child_errno == 0) == (0 < pid)); 654 eassert ((child_errno == 0) == (0 < pid));
655 655
656 if (pid > 0) 656 if (pid > 0)
@@ -1412,14 +1412,15 @@ emacs_posix_spawn_init_attributes (posix_spawnattr_t *attributes,
1412int 1412int
1413emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, 1413emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1414 char **argv, char **envp, const char *cwd, 1414 char **argv, char **envp, const char *cwd,
1415 const char *pty, const sigset_t *oldset) 1415 const char *pty_name, bool pty_in, bool pty_out,
1416 const sigset_t *oldset)
1416{ 1417{
1417#if USABLE_POSIX_SPAWN 1418#if USABLE_POSIX_SPAWN
1418 /* Prefer the simpler `posix_spawn' if available. `posix_spawn' 1419 /* Prefer the simpler `posix_spawn' if available. `posix_spawn'
1419 doesn't yet support setting up pseudoterminals, so we fall back 1420 doesn't yet support setting up pseudoterminals, so we fall back
1420 to `vfork' if we're supposed to use a pseudoterminal. */ 1421 to `vfork' if we're supposed to use a pseudoterminal. */
1421 1422
1422 bool use_posix_spawn = pty == NULL; 1423 bool use_posix_spawn = pty_name == NULL;
1423 1424
1424 posix_spawn_file_actions_t actions; 1425 posix_spawn_file_actions_t actions;
1425 posix_spawnattr_t attributes; 1426 posix_spawnattr_t attributes;
@@ -1473,7 +1474,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1473 /* vfork, and prevent local vars from being clobbered by the vfork. */ 1474 /* vfork, and prevent local vars from being clobbered by the vfork. */
1474 pid_t *volatile newpid_volatile = newpid; 1475 pid_t *volatile newpid_volatile = newpid;
1475 const char *volatile cwd_volatile = cwd; 1476 const char *volatile cwd_volatile = cwd;
1476 const char *volatile pty_volatile = pty; 1477 const char *volatile ptyname_volatile = pty_name;
1478 bool volatile ptyin_volatile = pty_in;
1479 bool volatile ptyout_volatile = pty_out;
1477 char **volatile argv_volatile = argv; 1480 char **volatile argv_volatile = argv;
1478 int volatile stdin_volatile = std_in; 1481 int volatile stdin_volatile = std_in;
1479 int volatile stdout_volatile = std_out; 1482 int volatile stdout_volatile = std_out;
@@ -1495,7 +1498,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1495 1498
1496 newpid = newpid_volatile; 1499 newpid = newpid_volatile;
1497 cwd = cwd_volatile; 1500 cwd = cwd_volatile;
1498 pty = pty_volatile; 1501 pty_name = ptyname_volatile;
1502 pty_in = ptyin_volatile;
1503 pty_out = ptyout_volatile;
1499 argv = argv_volatile; 1504 argv = argv_volatile;
1500 std_in = stdin_volatile; 1505 std_in = stdin_volatile;
1501 std_out = stdout_volatile; 1506 std_out = stdout_volatile;
@@ -1506,13 +1511,12 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1506 if (pid == 0) 1511 if (pid == 0)
1507#endif /* not WINDOWSNT */ 1512#endif /* not WINDOWSNT */
1508 { 1513 {
1509 bool pty_flag = pty != NULL;
1510 /* Make the pty be the controlling terminal of the process. */ 1514 /* Make the pty be the controlling terminal of the process. */
1511#ifdef HAVE_PTYS 1515#ifdef HAVE_PTYS
1512 dissociate_controlling_tty (); 1516 dissociate_controlling_tty ();
1513 1517
1514 /* Make the pty's terminal the controlling terminal. */ 1518 /* Make the pty's terminal the controlling terminal. */
1515 if (pty_flag && std_in >= 0) 1519 if (pty_in && std_in >= 0)
1516 { 1520 {
1517#ifdef TIOCSCTTY 1521#ifdef TIOCSCTTY
1518 /* We ignore the return value 1522 /* We ignore the return value
@@ -1521,7 +1525,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1521#endif 1525#endif
1522 } 1526 }
1523#if defined (LDISC1) 1527#if defined (LDISC1)
1524 if (pty_flag && std_in >= 0) 1528 if (pty_in && std_in >= 0)
1525 { 1529 {
1526 struct termios t; 1530 struct termios t;
1527 tcgetattr (std_in, &t); 1531 tcgetattr (std_in, &t);
@@ -1531,7 +1535,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1531 } 1535 }
1532#else 1536#else
1533#if defined (NTTYDISC) && defined (TIOCSETD) 1537#if defined (NTTYDISC) && defined (TIOCSETD)
1534 if (pty_flag && std_in >= 0) 1538 if (pty_in && std_in >= 0)
1535 { 1539 {
1536 /* Use new line discipline. */ 1540 /* Use new line discipline. */
1537 int ldisc = NTTYDISC; 1541 int ldisc = NTTYDISC;
@@ -1548,18 +1552,21 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1548 both TIOCSCTTY is defined. */ 1552 both TIOCSCTTY is defined. */
1549 /* Now close the pty (if we had it open) and reopen it. 1553 /* Now close the pty (if we had it open) and reopen it.
1550 This makes the pty the controlling terminal of the subprocess. */ 1554 This makes the pty the controlling terminal of the subprocess. */
1551 if (pty_flag) 1555 if (pty_name)
1552 { 1556 {
1553 1557
1554 /* I wonder if emacs_close (emacs_open (pty, ...)) 1558 /* I wonder if emacs_close (emacs_open (pty, ...))
1555 would work? */ 1559 would work? */
1556 if (std_in >= 0) 1560 if (pty_in && std_in >= 0)
1557 emacs_close (std_in); 1561 emacs_close (std_in);
1558 std_out = std_in = emacs_open_noquit (pty, O_RDWR, 0); 1562 int ptyfd = emacs_open_noquit (pty_name, O_RDWR, 0);
1559 1563 if (pty_in)
1564 std_in = ptyfd;
1565 if (pty_out)
1566 std_out = ptyfd;
1560 if (std_in < 0) 1567 if (std_in < 0)
1561 { 1568 {
1562 emacs_perror (pty); 1569 emacs_perror (pty_name);
1563 _exit (EXIT_CANCELED); 1570 _exit (EXIT_CANCELED);
1564 } 1571 }
1565 1572
@@ -1599,7 +1606,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
1599 /* Stop blocking SIGCHLD in the child. */ 1606 /* Stop blocking SIGCHLD in the child. */
1600 unblock_child_signal (oldset); 1607 unblock_child_signal (oldset);
1601 1608
1602 if (pty_flag) 1609 if (pty_out)
1603 child_setup_tty (std_out); 1610 child_setup_tty (std_out);
1604#endif 1611#endif
1605 1612
diff --git a/src/lisp.h b/src/lisp.h
index 8e36620fe53..fe6e98843d1 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4943,7 +4943,8 @@ extern void setup_process_coding_systems (Lisp_Object);
4943#endif 4943#endif
4944 4944
4945extern int emacs_spawn (pid_t *, int, int, int, char **, char **, 4945extern int emacs_spawn (pid_t *, int, int, int, char **, char **,
4946 const char *, const char *, const sigset_t *); 4946 const char *, const char *, bool, bool,
4947 const sigset_t *);
4947extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL; 4948extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL;
4948extern void init_callproc_1 (void); 4949extern void init_callproc_1 (void);
4949extern void init_callproc (void); 4950extern void init_callproc (void);
diff --git a/src/process.c b/src/process.c
index 1ac5a509e56..68dbd8b68bd 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1316,6 +1316,19 @@ set_process_filter_masks (struct Lisp_Process *p)
1316 add_process_read_fd (p->infd); 1316 add_process_read_fd (p->infd);
1317} 1317}
1318 1318
1319static bool
1320is_pty_from_symbol (Lisp_Object symbol)
1321{
1322 if (EQ (symbol, Qpty))
1323 return true;
1324 else if (EQ (symbol, Qpipe))
1325 return false;
1326 else if (NILP (symbol))
1327 return !NILP (Vprocess_connection_type);
1328 else
1329 report_file_error ("Unknown connection type", symbol);
1330}
1331
1319DEFUN ("set-process-filter", Fset_process_filter, Sset_process_filter, 1332DEFUN ("set-process-filter", Fset_process_filter, Sset_process_filter,
1320 2, 2, 0, 1333 2, 2, 0,
1321 doc: /* Give PROCESS the filter function FILTER; nil means default. 1334 doc: /* Give PROCESS the filter function FILTER; nil means default.
@@ -1741,15 +1754,18 @@ signals to stop and continue a process.
1741:connection-type TYPE -- TYPE is control type of device used to 1754:connection-type TYPE -- TYPE is control type of device used to
1742communicate with subprocesses. Values are `pipe' to use a pipe, `pty' 1755communicate with subprocesses. Values are `pipe' to use a pipe, `pty'
1743to use a pty, or nil to use the default specified through 1756to use a pty, or nil to use the default specified through
1744`process-connection-type'. 1757`process-connection-type'. If TYPE is a cons (INPUT . OUTPUT), then
1758INPUT will be used for standard input and OUTPUT for standard output
1759(and standard error if `:stderr' is nil).
1745 1760
1746:filter FILTER -- Install FILTER as the process filter. 1761:filter FILTER -- Install FILTER as the process filter.
1747 1762
1748:sentinel SENTINEL -- Install SENTINEL as the process sentinel. 1763:sentinel SENTINEL -- Install SENTINEL as the process sentinel.
1749 1764
1750:stderr STDERR -- STDERR is either a buffer or a pipe process attached 1765:stderr STDERR -- STDERR is either a buffer or a pipe process attached
1751to the standard error of subprocess. Specifying this implies 1766to the standard error of subprocess. When specifying this, the
1752`:connection-type' is set to `pipe'. If STDERR is nil, standard error 1767subprocess's standard error will always communicate via a pipe, no
1768matter the value of `:connection-type'. If STDERR is nil, standard error
1753is mixed with standard output and sent to BUFFER or FILTER. (Note 1769is mixed with standard output and sent to BUFFER or FILTER. (Note
1754that specifying :stderr will create a new, separate (but associated) 1770that specifying :stderr will create a new, separate (but associated)
1755process, with its own filter and sentinel. See 1771process, with its own filter and sentinel. See
@@ -1845,22 +1861,20 @@ usage: (make-process &rest ARGS) */)
1845 CHECK_TYPE (NILP (tem), Qnull, tem); 1861 CHECK_TYPE (NILP (tem), Qnull, tem);
1846 1862
1847 tem = plist_get (contact, QCconnection_type); 1863 tem = plist_get (contact, QCconnection_type);
1848 if (EQ (tem, Qpty)) 1864 if (CONSP (tem))
1849 XPROCESS (proc)->pty_flag = true; 1865 {
1850 else if (EQ (tem, Qpipe)) 1866 XPROCESS (proc)->pty_in = is_pty_from_symbol (XCAR (tem));
1851 XPROCESS (proc)->pty_flag = false; 1867 XPROCESS (proc)->pty_out = is_pty_from_symbol (XCDR (tem));
1852 else if (NILP (tem)) 1868 }
1853 XPROCESS (proc)->pty_flag = !NILP (Vprocess_connection_type);
1854 else 1869 else
1855 report_file_error ("Unknown connection type", tem);
1856
1857 if (!NILP (stderrproc))
1858 { 1870 {
1859 pset_stderrproc (XPROCESS (proc), stderrproc); 1871 XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out =
1860 1872 is_pty_from_symbol (tem);
1861 XPROCESS (proc)->pty_flag = false;
1862 } 1873 }
1863 1874
1875 if (!NILP (stderrproc))
1876 pset_stderrproc (XPROCESS (proc), stderrproc);
1877
1864#ifdef HAVE_GNUTLS 1878#ifdef HAVE_GNUTLS
1865 /* AKA GNUTLS_INITSTAGE(proc). */ 1879 /* AKA GNUTLS_INITSTAGE(proc). */
1866 verify (GNUTLS_STAGE_EMPTY == 0); 1880 verify (GNUTLS_STAGE_EMPTY == 0);
@@ -2099,66 +2113,80 @@ static void
2099create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir) 2113create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
2100{ 2114{
2101 struct Lisp_Process *p = XPROCESS (process); 2115 struct Lisp_Process *p = XPROCESS (process);
2102 int inchannel, outchannel; 2116 int inchannel = -1, outchannel = -1;
2103 pid_t pid = -1; 2117 pid_t pid = -1;
2104 int vfork_errno; 2118 int vfork_errno;
2105 int forkin, forkout, forkerr = -1; 2119 int forkin, forkout, forkerr = -1;
2106 bool pty_flag = 0; 2120 bool pty_in = false, pty_out = false;
2107 char pty_name[PTY_NAME_SIZE]; 2121 char pty_name[PTY_NAME_SIZE];
2108 Lisp_Object lisp_pty_name = Qnil; 2122 Lisp_Object lisp_pty_name = Qnil;
2123 int ptychannel = -1, pty_tty = -1;
2109 sigset_t oldset; 2124 sigset_t oldset;
2110 2125
2111 /* Ensure that the SIGCHLD handler can notify 2126 /* Ensure that the SIGCHLD handler can notify
2112 `wait_reading_process_output'. */ 2127 `wait_reading_process_output'. */
2113 child_signal_init (); 2128 child_signal_init ();
2114 2129
2115 inchannel = outchannel = -1; 2130 if (p->pty_in || p->pty_out)
2116 2131 ptychannel = allocate_pty (pty_name);
2117 if (p->pty_flag)
2118 outchannel = inchannel = allocate_pty (pty_name);
2119 2132
2120 if (inchannel >= 0) 2133 if (ptychannel >= 0)
2121 { 2134 {
2122 p->open_fd[READ_FROM_SUBPROCESS] = inchannel;
2123#if ! defined (USG) || defined (USG_SUBTTY_WORKS) 2135#if ! defined (USG) || defined (USG_SUBTTY_WORKS)
2124 /* On most USG systems it does not work to open the pty's tty here, 2136 /* On most USG systems it does not work to open the pty's tty here,
2125 then close it and reopen it in the child. */ 2137 then close it and reopen it in the child. */
2126 /* Don't let this terminal become our controlling terminal 2138 /* Don't let this terminal become our controlling terminal
2127 (in case we don't have one). */ 2139 (in case we don't have one). */
2128 forkout = forkin = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0); 2140 pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
2129 if (forkin < 0) 2141 if (pty_tty < 0)
2130 report_file_error ("Opening pty", Qnil); 2142 report_file_error ("Opening pty", Qnil);
2131 p->open_fd[SUBPROCESS_STDIN] = forkin;
2132#else
2133 forkin = forkout = -1;
2134#endif /* not USG, or USG_SUBTTY_WORKS */ 2143#endif /* not USG, or USG_SUBTTY_WORKS */
2135 pty_flag = 1; 2144 pty_in = p->pty_in;
2145 pty_out = p->pty_out;
2136 lisp_pty_name = build_string (pty_name); 2146 lisp_pty_name = build_string (pty_name);
2137 } 2147 }
2148
2149 /* Set up stdin for the child process. */
2150 if (ptychannel >= 0 && p->pty_in)
2151 {
2152 p->open_fd[SUBPROCESS_STDIN] = forkin = pty_tty;
2153 outchannel = ptychannel;
2154 }
2138 else 2155 else
2139 { 2156 {
2140 if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0 2157 if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0)
2141 || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
2142 report_file_error ("Creating pipe", Qnil); 2158 report_file_error ("Creating pipe", Qnil);
2143 forkin = p->open_fd[SUBPROCESS_STDIN]; 2159 forkin = p->open_fd[SUBPROCESS_STDIN];
2144 outchannel = p->open_fd[WRITE_TO_SUBPROCESS]; 2160 outchannel = p->open_fd[WRITE_TO_SUBPROCESS];
2161 }
2162
2163 /* Set up stdout for the child process. */
2164 if (ptychannel >= 0 && p->pty_out)
2165 {
2166 forkout = pty_tty;
2167 p->open_fd[READ_FROM_SUBPROCESS] = inchannel = ptychannel;
2168 }
2169 else
2170 {
2171 if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
2172 report_file_error ("Creating pipe", Qnil);
2145 inchannel = p->open_fd[READ_FROM_SUBPROCESS]; 2173 inchannel = p->open_fd[READ_FROM_SUBPROCESS];
2146 forkout = p->open_fd[SUBPROCESS_STDOUT]; 2174 forkout = p->open_fd[SUBPROCESS_STDOUT];
2147 2175
2148#if defined(GNU_LINUX) && defined(F_SETPIPE_SZ) 2176#if defined(GNU_LINUX) && defined(F_SETPIPE_SZ)
2149 fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max); 2177 fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max);
2150#endif 2178#endif
2179 }
2151 2180
2152 if (!NILP (p->stderrproc)) 2181 if (!NILP (p->stderrproc))
2153 { 2182 {
2154 struct Lisp_Process *pp = XPROCESS (p->stderrproc); 2183 struct Lisp_Process *pp = XPROCESS (p->stderrproc);
2155 2184
2156 forkerr = pp->open_fd[SUBPROCESS_STDOUT]; 2185 forkerr = pp->open_fd[SUBPROCESS_STDOUT];
2157 2186
2158 /* Close unnecessary file descriptors. */ 2187 /* Close unnecessary file descriptors. */
2159 close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]); 2188 close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
2160 close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]); 2189 close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
2161 }
2162 } 2190 }
2163 2191
2164 if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel) 2192 if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel)
@@ -2183,7 +2211,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
2183 we just reopen the device (see emacs_get_tty_pgrp) as this is 2211 we just reopen the device (see emacs_get_tty_pgrp) as this is
2184 more portable (see USG_SUBTTY_WORKS above). */ 2212 more portable (see USG_SUBTTY_WORKS above). */
2185 2213
2186 p->pty_flag = pty_flag; 2214 p->pty_in = pty_in;
2215 p->pty_out = pty_out;
2187 pset_status (p, Qrun); 2216 pset_status (p, Qrun);
2188 2217
2189 if (!EQ (p->command, Qt) 2218 if (!EQ (p->command, Qt)
@@ -2199,13 +2228,15 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
2199 block_input (); 2228 block_input ();
2200 block_child_signal (&oldset); 2229 block_child_signal (&oldset);
2201 2230
2202 pty_flag = p->pty_flag; 2231 pty_in = p->pty_in;
2203 eassert (pty_flag == ! NILP (lisp_pty_name)); 2232 pty_out = p->pty_out;
2233 eassert ((pty_in || pty_out) == ! NILP (lisp_pty_name));
2204 2234
2205 vfork_errno 2235 vfork_errno
2206 = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env, 2236 = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env,
2207 SSDATA (current_dir), 2237 SSDATA (current_dir),
2208 pty_flag ? SSDATA (lisp_pty_name) : NULL, &oldset); 2238 pty_in || pty_out ? SSDATA (lisp_pty_name) : NULL,
2239 pty_in, pty_out, &oldset);
2209 2240
2210 eassert ((vfork_errno == 0) == (0 < pid)); 2241 eassert ((vfork_errno == 0) == (0 < pid));
2211 2242
@@ -2263,7 +2294,7 @@ create_pty (Lisp_Object process)
2263{ 2294{
2264 struct Lisp_Process *p = XPROCESS (process); 2295 struct Lisp_Process *p = XPROCESS (process);
2265 char pty_name[PTY_NAME_SIZE]; 2296 char pty_name[PTY_NAME_SIZE];
2266 int pty_fd = !p->pty_flag ? -1 : allocate_pty (pty_name); 2297 int pty_fd = !(p->pty_in || p->pty_out) ? -1 : allocate_pty (pty_name);
2267 2298
2268 if (pty_fd >= 0) 2299 if (pty_fd >= 0)
2269 { 2300 {
@@ -2301,7 +2332,7 @@ create_pty (Lisp_Object process)
2301 we just reopen the device (see emacs_get_tty_pgrp) as this is 2332 we just reopen the device (see emacs_get_tty_pgrp) as this is
2302 more portable (see USG_SUBTTY_WORKS above). */ 2333 more portable (see USG_SUBTTY_WORKS above). */
2303 2334
2304 p->pty_flag = 1; 2335 p->pty_in = p->pty_out = true;
2305 pset_status (p, Qrun); 2336 pset_status (p, Qrun);
2306 setup_process_coding_systems (process); 2337 setup_process_coding_systems (process);
2307 2338
@@ -2412,7 +2443,7 @@ usage: (make-pipe-process &rest ARGS) */)
2412 p->kill_without_query = 1; 2443 p->kill_without_query = 1;
2413 if (tem = plist_get (contact, QCstop), !NILP (tem)) 2444 if (tem = plist_get (contact, QCstop), !NILP (tem))
2414 pset_command (p, Qt); 2445 pset_command (p, Qt);
2415 eassert (! p->pty_flag); 2446 eassert (! p->pty_in && ! p->pty_out);
2416 2447
2417 if (!EQ (p->command, Qt) 2448 if (!EQ (p->command, Qt)
2418 && !EQ (p->filter, Qt)) 2449 && !EQ (p->filter, Qt))
@@ -3147,7 +3178,7 @@ usage: (make-serial-process &rest ARGS) */)
3147 p->kill_without_query = 1; 3178 p->kill_without_query = 1;
3148 if (tem = plist_get (contact, QCstop), !NILP (tem)) 3179 if (tem = plist_get (contact, QCstop), !NILP (tem))
3149 pset_command (p, Qt); 3180 pset_command (p, Qt);
3150 eassert (! p->pty_flag); 3181 eassert (! p->pty_in && ! p->pty_out);
3151 3182
3152 if (!EQ (p->command, Qt) 3183 if (!EQ (p->command, Qt)
3153 && !EQ (p->filter, Qt)) 3184 && !EQ (p->filter, Qt))
@@ -6808,7 +6839,7 @@ process_send_signal (Lisp_Object process, int signo, Lisp_Object current_group,
6808 error ("Process %s is not active", 6839 error ("Process %s is not active",
6809 SDATA (p->name)); 6840 SDATA (p->name));
6810 6841
6811 if (!p->pty_flag) 6842 if (! p->pty_in)
6812 current_group = Qnil; 6843 current_group = Qnil;
6813 6844
6814 /* If we are using pgrps, get a pgrp number and make it negative. */ 6845 /* If we are using pgrps, get a pgrp number and make it negative. */
@@ -7177,7 +7208,7 @@ process has been transmitted to the serial port. */)
7177 send_process (proc, "", 0, Qnil); 7208 send_process (proc, "", 0, Qnil);
7178 } 7209 }
7179 7210
7180 if (XPROCESS (proc)->pty_flag) 7211 if (XPROCESS (proc)->pty_in)
7181 send_process (proc, "\004", 1, Qnil); 7212 send_process (proc, "\004", 1, Qnil);
7182 else if (EQ (XPROCESS (proc)->type, Qserial)) 7213 else if (EQ (XPROCESS (proc)->type, Qserial))
7183 { 7214 {
diff --git a/src/process.h b/src/process.h
index 392b661ce69..92baf0c4cb9 100644
--- a/src/process.h
+++ b/src/process.h
@@ -156,8 +156,9 @@ struct Lisp_Process
156 /* True means kill silently if Emacs is exited. 156 /* True means kill silently if Emacs is exited.
157 This is the inverse of the `query-on-exit' flag. */ 157 This is the inverse of the `query-on-exit' flag. */
158 bool_bf kill_without_query : 1; 158 bool_bf kill_without_query : 1;
159 /* True if communicating through a pty. */ 159 /* True if communicating through a pty for input or output. */
160 bool_bf pty_flag : 1; 160 bool_bf pty_in : 1;
161 bool_bf pty_out : 1;
161 /* Flag to set coding-system of the process buffer from the 162 /* Flag to set coding-system of the process buffer from the
162 coding_system used to decode process output. */ 163 coding_system used to decode process output. */
163 bool_bf inherit_coding_system_flag : 1; 164 bool_bf inherit_coding_system_flag : 1;
diff --git a/test/lisp/eshell/esh-proc-tests.el b/test/lisp/eshell/esh-proc-tests.el
index 7f461d1813c..734bb91a6a5 100644
--- a/test/lisp/eshell/esh-proc-tests.el
+++ b/test/lisp/eshell/esh-proc-tests.el
@@ -28,6 +28,15 @@
28 (file-name-directory (or load-file-name 28 (file-name-directory (or load-file-name
29 default-directory)))) 29 default-directory))))
30 30
31(defvar esh-proc-test--detect-pty-cmd
32 (concat "sh -c '"
33 "if [ -t 0 ]; then echo stdin; fi; "
34 "if [ -t 1 ]; then echo stdout; fi; "
35 "if [ -t 2 ]; then echo stderr; fi"
36 "'"))
37
38;;; Tests:
39
31(ert-deftest esh-proc-test/sigpipe-exits-process () 40(ert-deftest esh-proc-test/sigpipe-exits-process ()
32 "Test that a SIGPIPE is properly sent to a process if a pipe closes" 41 "Test that a SIGPIPE is properly sent to a process if a pipe closes"
33 (skip-unless (and (executable-find "sh") 42 (skip-unless (and (executable-find "sh")
@@ -44,6 +53,40 @@
44 (eshell-wait-for-subprocess t) 53 (eshell-wait-for-subprocess t)
45 (should (eq (process-list) nil)))) 54 (should (eq (process-list) nil))))
46 55
56(ert-deftest esh-proc-test/pipeline-connection-type/no-pipeline ()
57 "Test that all streams are PTYs when a command is not in a pipeline."
58 (skip-unless (executable-find "sh"))
59 (should (equal (eshell-test-command-result esh-proc-test--detect-pty-cmd)
60 ;; PTYs aren't supported on MS-Windows.
61 (unless (eq system-type 'windows-nt)
62 "stdin\nstdout\nstderr\n"))))
63
64(ert-deftest esh-proc-test/pipeline-connection-type/first ()
65 "Test that only stdin is a PTY when a command starts a pipeline."
66 (skip-unless (and (executable-find "sh")
67 (executable-find "cat")))
68 (should (equal (eshell-test-command-result
69 (concat esh-proc-test--detect-pty-cmd " | cat"))
70 (unless (eq system-type 'windows-nt)
71 "stdin\n"))))
72
73(ert-deftest esh-proc-test/pipeline-connection-type/middle ()
74 "Test that all streams are pipes when a command is in the middle of a
75pipeline."
76 (skip-unless (and (executable-find "sh")
77 (executable-find "cat")))
78 (should (equal (eshell-test-command-result
79 (concat "echo | " esh-proc-test--detect-pty-cmd " | cat"))
80 nil)))
81
82(ert-deftest esh-proc-test/pipeline-connection-type/last ()
83 "Test that only output streams are PTYs when a command ends a pipeline."
84 (skip-unless (executable-find "sh"))
85 (should (equal (eshell-test-command-result
86 (concat "echo | " esh-proc-test--detect-pty-cmd))
87 (unless (eq system-type 'windows-nt)
88 "stdout\nstderr\n"))))
89
47(ert-deftest esh-proc-test/kill-pipeline () 90(ert-deftest esh-proc-test/kill-pipeline ()
48 "Test that killing a pipeline of processes only emits a single 91 "Test that killing a pipeline of processes only emits a single
49prompt. See bug#54136." 92prompt. See bug#54136."
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index aab95b2d733..b801563feb7 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -38,10 +38,11 @@
38;; Timeout in seconds; the test fails if the timeout is reached. 38;; Timeout in seconds; the test fails if the timeout is reached.
39(defvar process-test-sentinel-wait-timeout 2.0) 39(defvar process-test-sentinel-wait-timeout 2.0)
40 40
41;; Start a process that exits immediately. Call WAIT-FUNCTION, 41(defun process-test-wait-for-sentinel (proc exit-status &optional wait-function)
42;; 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.
43(defun process-test-sentinel-wait-function-working-p (wait-function) 43Call WAIT-FUNCTION, possibly multiple times, to wait for the
44 (let ((proc (start-process "test" nil "bash" "-c" "exit 20")) 44process to complete."
45 (let ((wait-function (or wait-function #'accept-process-output))
45 (sentinel-called nil) 46 (sentinel-called nil)
46 (start-time (float-time))) 47 (start-time (float-time)))
47 (set-process-sentinel proc (lambda (_proc _msg) 48 (set-process-sentinel proc (lambda (_proc _msg)
@@ -50,21 +51,22 @@
50 (> (- (float-time) start-time) 51 (> (- (float-time) start-time)
51 process-test-sentinel-wait-timeout))) 52 process-test-sentinel-wait-timeout)))
52 (funcall wait-function)) 53 (funcall wait-function))
53 (cl-assert (eq (process-status proc) 'exit)) 54 (should sentinel-called)
54 (cl-assert (= (process-exit-status proc) 20)) 55 (should (eq (process-status proc) 'exit))
55 sentinel-called)) 56 (should (= (process-exit-status proc) exit-status))))
56 57
57(ert-deftest process-test-sentinel-accept-process-output () 58(ert-deftest process-test-sentinel-accept-process-output ()
58 (skip-unless (executable-find "bash")) 59 (skip-unless (executable-find "bash"))
59 (with-timeout (60 (ert-fail "Test timed out")) 60 (with-timeout (60 (ert-fail "Test timed out"))
60 (should (process-test-sentinel-wait-function-working-p 61 (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
61 #'accept-process-output)))) 62 (should (process-test-wait-for-sentinel proc 20)))))
62 63
63(ert-deftest process-test-sentinel-sit-for () 64(ert-deftest process-test-sentinel-sit-for ()
64 (skip-unless (executable-find "bash")) 65 (skip-unless (executable-find "bash"))
65 (with-timeout (60 (ert-fail "Test timed out")) 66 (with-timeout (60 (ert-fail "Test timed out"))
66 (should 67 (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
67 (process-test-sentinel-wait-function-working-p (lambda () (sit-for 0.01 t)))))) 68 (should (process-test-wait-for-sentinel
69 proc 20 (lambda () (sit-for 0.01 t)))))))
68 70
69(when (eq system-type 'windows-nt) 71(when (eq system-type 'windows-nt)
70 (ert-deftest process-test-quoted-batfile () 72 (ert-deftest process-test-quoted-batfile ()
@@ -97,17 +99,8 @@
97 "echo hello stderr! >&2; " 99 "echo hello stderr! >&2; "
98 "exit 20")) 100 "exit 20"))
99 :buffer stdout-buffer 101 :buffer stdout-buffer
100 :stderr stderr-buffer)) 102 :stderr stderr-buffer)))
101 (sentinel-called nil) 103 (process-test-wait-for-sentinel proc 20)
102 (start-time (float-time)))
103 (set-process-sentinel proc (lambda (_proc _msg)
104 (setq sentinel-called t)))
105 (while (not (or sentinel-called
106 (> (- (float-time) start-time)
107 process-test-sentinel-wait-timeout)))
108 (accept-process-output))
109 (cl-assert (eq (process-status proc) 'exit))
110 (cl-assert (= (process-exit-status proc) 20))
111 (should (with-current-buffer stdout-buffer 104 (should (with-current-buffer stdout-buffer
112 (goto-char (point-min)) 105 (goto-char (point-min))
113 (looking-at "hello stdout!"))) 106 (looking-at "hello stdout!")))
@@ -118,8 +111,7 @@
118(ert-deftest process-test-stderr-filter () 111(ert-deftest process-test-stderr-filter ()
119 (skip-unless (executable-find "bash")) 112 (skip-unless (executable-find "bash"))
120 (with-timeout (60 (ert-fail "Test timed out")) 113 (with-timeout (60 (ert-fail "Test timed out"))
121 (let* ((sentinel-called nil) 114 (let* ((stderr-sentinel-called nil)
122 (stderr-sentinel-called nil)
123 (stdout-output nil) 115 (stdout-output nil)
124 (stderr-output nil) 116 (stderr-output nil)
125 (stdout-buffer (generate-new-buffer "*stdout*")) 117 (stdout-buffer (generate-new-buffer "*stdout*"))
@@ -131,23 +123,14 @@
131 (concat "echo hello stdout!; " 123 (concat "echo hello stdout!; "
132 "echo hello stderr! >&2; " 124 "echo hello stderr! >&2; "
133 "exit 20")) 125 "exit 20"))
134 :stderr stderr-proc)) 126 :stderr stderr-proc)))
135 (start-time (float-time)))
136 (set-process-filter proc (lambda (_proc input) 127 (set-process-filter proc (lambda (_proc input)
137 (push input stdout-output))) 128 (push input stdout-output)))
138 (set-process-sentinel proc (lambda (_proc _msg)
139 (setq sentinel-called t)))
140 (set-process-filter stderr-proc (lambda (_proc input) 129 (set-process-filter stderr-proc (lambda (_proc input)
141 (push input stderr-output))) 130 (push input stderr-output)))
142 (set-process-sentinel stderr-proc (lambda (_proc _input) 131 (set-process-sentinel stderr-proc (lambda (_proc _input)
143 (setq stderr-sentinel-called t))) 132 (setq stderr-sentinel-called t)))
144 (while (not (or sentinel-called 133 (process-test-wait-for-sentinel proc 20)
145 (> (- (float-time) start-time)
146 process-test-sentinel-wait-timeout)))
147 (accept-process-output))
148 (cl-assert (eq (process-status proc) 'exit))
149 (cl-assert (= (process-exit-status proc) 20))
150 (should sentinel-called)
151 (should (equal 1 (with-current-buffer stdout-buffer 134 (should (equal 1 (with-current-buffer stdout-buffer
152 (point-max)))) 135 (point-max))))
153 (should (equal "hello stdout!\n" 136 (should (equal "hello stdout!\n"
@@ -289,6 +272,74 @@
289 (error :got-error)))) 272 (error :got-error))))
290 (should have-called-debugger)))) 273 (should have-called-debugger))))
291 274
275(defun make-process/test-connection-type (ttys &rest args)
276 "Make a process and check whether its standard streams match TTYS.
277This calls `make-process', passing ARGS to adjust how the process
278is created. TTYS should be a list of 3 boolean values,
279indicating whether the subprocess's stdin, stdout, and stderr
280should be a TTY, respectively."
281 (declare (indent 1))
282 (let* (;; MS-Windows doesn't support communicating via pty.
283 (ttys (if (eq system-type 'windows-nt) '(nil nil nil) ttys))
284 (expected-output (concat (and (nth 0 ttys) "stdin\n")
285 (and (nth 1 ttys) "stdout\n")
286 (and (nth 2 ttys) "stderr\n")))
287 (stdout-buffer (generate-new-buffer "*stdout*"))
288 (proc (apply
289 #'make-process
290 :name "test"
291 :command (list "sh" "-c"
292 (concat "if [ -t 0 ]; then echo stdin; fi; "
293 "if [ -t 1 ]; then echo stdout; fi; "
294 "if [ -t 2 ]; then echo stderr; fi"))
295 :buffer stdout-buffer
296 args)))
297 (process-test-wait-for-sentinel proc 0)
298 (should (equal (with-current-buffer stdout-buffer (buffer-string))
299 expected-output))))
300
301(ert-deftest make-process/connection-type/pty ()
302 (skip-unless (executable-find "sh"))
303 (make-process/test-connection-type '(t t t)
304 :connection-type 'pty))
305
306(ert-deftest make-process/connection-type/pty-2 ()
307 (skip-unless (executable-find "sh"))
308 (make-process/test-connection-type '(t t t)
309 :connection-type '(pty . pty)))
310
311(ert-deftest make-process/connection-type/pipe ()
312 (skip-unless (executable-find "sh"))
313 (make-process/test-connection-type '(nil nil nil)
314 :connection-type 'pipe))
315
316(ert-deftest make-process/connection-type/pipe-2 ()
317 (skip-unless (executable-find "sh"))
318 (make-process/test-connection-type '(nil nil nil)
319 :connection-type '(pipe . pipe)))
320
321(ert-deftest make-process/connection-type/in-pty ()
322 (skip-unless (executable-find "sh"))
323 (make-process/test-connection-type '(t nil nil)
324 :connection-type '(pty . pipe)))
325
326(ert-deftest make-process/connection-type/out-pty ()
327 (skip-unless (executable-find "sh"))
328 (make-process/test-connection-type '(nil t t)
329 :connection-type '(pipe . pty)))
330
331(ert-deftest make-process/connection-type/pty-with-stderr-buffer ()
332 (skip-unless (executable-find "sh"))
333 (let ((stderr-buffer (generate-new-buffer "*stderr*")))
334 (make-process/test-connection-type '(t t nil)
335 :connection-type 'pty :stderr stderr-buffer)))
336
337(ert-deftest make-process/connection-type/out-pty-with-stderr-buffer ()
338 (skip-unless (executable-find "sh"))
339 (let ((stderr-buffer (generate-new-buffer "*stderr*")))
340 (make-process/test-connection-type '(nil t nil)
341 :connection-type '(pipe . pty) :stderr stderr-buffer)))
342
292(ert-deftest make-process/file-handler/found () 343(ert-deftest make-process/file-handler/found ()
293 "Check that the `:file-handler’ argument of `make-process’ 344 "Check that the `:file-handler’ argument of `make-process’
294works as expected if a file name handler is found." 345works as expected if a file name handler is found."