Commit f55ea05b authored by Daiki Ueno's avatar Daiki Ueno Committed by Daiki Ueno

Add facility to collect stderr of async subprocess

* src/w32.h (register_aux_fd): New function declaration.
* src/w32.c (register_aux_fd): New function.
* src/process.h (struct Lisp_Process): New member stderrproc.
* src/process.c (PIPECONN_P): New macro.
(PIPECONN1_P): New macro.
(Fdelete_process, Fprocess_status, Fset_process_buffer)
(Fset_process_filter, Fset_process_sentinel, Fstop_process)
(Fcontinue_process): Handle pipe process specially.
(create_process): Respect p->stderrproc.
(Fmake_pipe_process): New function.
(Fmake_process): Add new keyword argument :stderr.
(wait_reading_process_output): Specially handle a pipe process when
it gets an EOF.
(syms_of_process): Register Qpipe and Smake_pipe_process.

* doc/lispref/processes.texi (Asynchronous Processes): Document
`make-pipe-process' and `:stderr' keyword of `make-process'.

* lisp/subr.el (start-process): Suggest to use `make-process' handle
standard error separately.

* test/automated/process-tests.el (process-test-stderr-buffer)
(process-test-stderr-filter): New tests.

* etc/NEWS: Mention new process type `pipe' and its usage with the
`:stderr' keyword of `make-process'.
parent a2940cd4
......@@ -739,6 +739,58 @@ If @var{stopped} is non-@code{nil}, start the process in the
@item :filter @var{filter}
Initialize the process filter to @var{filter}.
@item :sentinel @var{sentinel}
Initialize the process sentinel to @var{sentinel}.
@item :stderr @var{stderr}
Associate @var{stderr} with the standard error of the process.
@var{stderr} is either a buffer or a pipe process created with
@code{make-pipe-process}.
@end table
The original argument list, modified with the actual connection
information, is available via the @code{process-contact} function.
@end defun
@defun make-pipe-process &rest args
This function creates a bidirectional pipe which can be attached to a
child process (currently only useful with the @code{:stderr} keyword
of @code{make-process}).
The arguments @var{args} are a list of keyword/argument pairs.
Omitting a keyword is always equivalent to specifying it with value
@code{nil}, except for @code{:coding}.
Here are the meaningful keywords:
@table @asis
@item :name @var{name}
Use the string @var{name} as the process name. It is modified if
necessary to make it unique.
@item :buffer @var{buffer}
Use @var{buffer} as the process buffer.
@item :coding @var{coding}
If @var{coding} is a symbol, it specifies the coding system to be
used for both reading and writing of data from and to the
connection. If @var{coding} is a cons cell
@w{@code{(@var{decoding} . @var{encoding})}}, then @var{decoding}
will be used for reading and @var{encoding} for writing.
If @var{coding} is @code{nil}, the default rules for finding the
coding system will apply. @xref{Default Coding Systems}.
@item :noquery @var{query-flag}
Initialize the process query flag to @var{query-flag}.
@xref{Query Before Exit}.
@item :stop @var{stopped}
If @var{stopped} is non-@code{nil}, start the process in the
``stopped'' state.
@item :filter @var{filter}
Initialize the process filter to @var{filter}.
@item :sentinel @var{sentinel}
Initialize the process sentinel to @var{sentinel}.
@end table
......
......@@ -674,6 +674,10 @@ word syntax, use `\sw' instead.
* Lisp Changes in Emacs 25.1
** New process type `pipe', which can be used in combination with the
`:stderr' keyword of make-process to handle standard error output
of subprocess.
** New function `make-process' provides an alternative interface to
`start-process'. It allows programs to set process parameters such as
process filter, sentinel, etc., through keyword arguments (similar to
......
......@@ -1936,9 +1936,9 @@ PROGRAM is the program file name. It is searched for in `exec-path'
\(which see). If nil, just associate a pty with the buffer. Remaining
arguments are strings to give program as arguments.
If you want to separate standard output from standard error, invoke
the command through a shell and redirect one of them using the shell
syntax."
If you want to separate standard output from standard error, use
`make-process' or invoke the command through a shell and redirect
one of them using the shell syntax."
(unless (fboundp 'make-process)
(error "Emacs was compiled without subprocess support"))
(apply #'make-process
......
This diff is collapsed.
......@@ -105,6 +105,9 @@ struct Lisp_Process
Lisp_Object gnutls_cred_type;
#endif
/* Pipe process attached to the standard error of this process. */
Lisp_Object stderrproc;
/* After this point, there are no Lisp_Objects any more. */
/* alloc.c assumes that `pid' is the first such non-Lisp slot. */
......
......@@ -9473,6 +9473,26 @@ serial_configure (struct Lisp_Process *p, Lisp_Object contact)
pset_childp (p, childp2);
}
/* For make-pipe-process */
void
register_aux_fd (int infd)
{
child_process *cp;
cp = new_child ();
if (!cp)
error ("Could not create child process");
cp->fd = infd;
cp->status = STATUS_READ_ACKNOWLEDGED;
if (fd_info[ infd ].cp != NULL)
{
error ("fd_info[fd = %d] is already in use", infd);
}
fd_info[ infd ].cp = cp;
fd_info[ infd ].hnd = (HANDLE) _get_osfhandle (infd);
}
#ifdef HAVE_GNUTLS
ssize_t
......
......@@ -202,6 +202,7 @@ extern int random (void);
extern int fchmod (int, mode_t);
extern int sys_rename_replace (char const *, char const *, BOOL);
extern int pipe2 (int *, int);
extern void register_aux_fd (int);
extern void set_process_dir (char *);
extern int sys_spawnve (int, char *, char **, char **);
......
......@@ -72,4 +72,74 @@
(should (string= (buffer-string) "arg1 = \"x &y\", arg2 = \n"))))
(when batfile (delete-file batfile))))))
(ert-deftest process-test-stderr-buffer ()
(skip-unless (executable-find "bash"))
(let* ((stdout-buffer (generate-new-buffer "*stdout*"))
(stderr-buffer (generate-new-buffer "*stderr*"))
(proc (make-process :name "test"
:command (list "bash" "-c"
(concat "echo hello stdout!; "
"echo hello stderr! >&2; "
"exit 20"))
:buffer stdout-buffer
:stderr stderr-buffer))
(sentinel-called nil)
(start-time (float-time)))
(set-process-sentinel proc (lambda (proc msg)
(setq sentinel-called t)))
(while (not (or sentinel-called
(> (- (float-time) start-time)
process-test-sentinel-wait-timeout)))
(accept-process-output))
(cl-assert (eq (process-status proc) 'exit))
(cl-assert (= (process-exit-status proc) 20))
(should (with-current-buffer stdout-buffer
(goto-char (point-min))
(looking-at "hello stdout!")))
(should (with-current-buffer stderr-buffer
(goto-char (point-min))
(looking-at "hello stderr!")))))
(ert-deftest process-test-stderr-filter ()
(skip-unless (executable-find "bash"))
(let* ((sentinel-called nil)
(stderr-sentinel-called nil)
(stdout-output nil)
(stderr-output nil)
(stdout-buffer (generate-new-buffer "*stdout*"))
(stderr-buffer (generate-new-buffer "*stderr*"))
(stderr-proc (make-pipe-process :name "stderr"
:buffer stderr-buffer))
(proc (make-process :name "test" :buffer stdout-buffer
:command (list "bash" "-c"
(concat "echo hello stdout!; "
"echo hello stderr! >&2; "
"exit 20"))
:stderr stderr-proc))
(start-time (float-time)))
(set-process-filter proc (lambda (proc input)
(push input stdout-output)))
(set-process-sentinel proc (lambda (proc msg)
(setq sentinel-called t)))
(set-process-filter stderr-proc (lambda (proc input)
(push input stderr-output)))
(set-process-sentinel stderr-proc (lambda (proc input)
(setq stderr-sentinel-called t)))
(while (not (or sentinel-called
(> (- (float-time) start-time)
process-test-sentinel-wait-timeout)))
(accept-process-output))
(cl-assert (eq (process-status proc) 'exit))
(cl-assert (= (process-exit-status proc) 20))
(should sentinel-called)
(should (equal 1 (with-current-buffer stdout-buffer
(point-max))))
(should (equal "hello stdout!\n"
(mapconcat #'identity (nreverse stdout-output) "")))
(should stderr-sentinel-called)
(should (equal 1 (with-current-buffer stderr-buffer
(point-max))))
(should (equal "hello stderr!\n"
(mapconcat #'identity (nreverse stderr-output) "")))))
(provide 'process-tests)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment