Subject: Re: bug#66066: 30.0.50; [PATCH] Add support for more-complex Eshell commands in the background

From: Jim Porter <[email-masked]>
Date: Sat, 23 Sep 2023 11:47:56 -0700

On 9/17/2023 9:45 PM, Jim Porter wrote:
> The attached patches fix this.

Attached are some new patches rebased onto my changes in bug#66164.

From f996f432f41ef89d5e419d33c81d4b3604243cce Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Fri, 22 Sep 2023 23:03:45 -0700
Subject: [PATCH 1/2] Don't print subjob messages when running an Eshell script
 in the background

* lisp/eshell/esh-proc.el (eshell-subjob-messages): New variable...
(eshell-record-process-object)
(eshell-remove-process-entry): ... check it.

* lisp/eshell/em-script.el (eshell-source-file): Set
'eshell-subjob-messages' to nil.

* lisp/eshell/esh-cmd.el (eshell-do-subjob): Set
'eshell-subjob-messages' to t.

* test/lisp/eshell/em-script-tests.el
(em-script-test/source-script/background): New test.
---
 lisp/eshell/em-script.el            |  7 ++++++-
 lisp/eshell/esh-cmd.el              |  5 ++++-
 lisp/eshell/esh-proc.el             | 18 +++++++++++-------
 test/lisp/eshell/em-script-tests.el | 13 +++++++++++++
 4 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/lisp/eshell/em-script.el b/lisp/eshell/em-script.el
index 9f6f720b8b0..3a4c315ad15 100644
--- a/lisp/eshell/em-script.el
+++ b/lisp/eshell/em-script.el
@@ -94,7 +94,12 @@ eshell-source-file
       (setq cmd `(eshell-as-subcommand ,cmd)))
     (throw 'eshell-replace-command
            `(let ((eshell-command-name ',file)
-                  (eshell-command-arguments ',args))
+                  (eshell-command-arguments ',args)
+                  ;; Don't print subjob messages by default.
+                  ;; Otherwise, if this function was called as a
+                  ;; subjob, then *all* commands in the script would
+                  ;; print start/stop messages.
+                  (eshell-subjob-messages nil))
               ,cmd))))
 
 (defun eshell/source (&rest args)
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 1d828bd7f82..fc7d54a758d 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -742,7 +742,10 @@ eshell-do-subjob
   "Evaluate a command OBJECT as a subjob.
 We indicate that the process was run in the background by returning it
 ensconced in a list."
-  `(let ((eshell-current-subjob-p t))
+  `(let ((eshell-current-subjob-p t)
+         ;; Print subjob messages.  This could have been cleared
+         ;; (e.g. by `eshell-source-file', which see).
+         (eshell-subjob-messages t))
      ,object))
 
 (defmacro eshell-commands (object &optional silent)
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index d15e1e7d09b..126c7d0f26e 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -100,6 +100,8 @@ eshell-kill-hook
 (defvar eshell-supports-asynchronous-processes (fboundp 'make-process)
   "Non-nil if Eshell can create asynchronous processes.")
 
+(defvar eshell-subjob-messages t
+  "Non-nil if we should print process start/end messages for subjobs.")
 (defvar eshell-current-subjob-p nil)
 
 (defvar eshell-process-list nil
@@ -243,8 +245,9 @@ eshell-insert-process
 
 (defsubst eshell-record-process-object (object)
   "Record OBJECT as now running."
-  (when (and (eshell-processp object)
-	     eshell-current-subjob-p)
+  (when (and eshell-subjob-messages
+             eshell-current-subjob-p
+             (eshell-processp object))
     (require 'esh-mode)
     (declare-function eshell-interactive-print "esh-mode" (string))
     (eshell-interactive-print
@@ -253,11 +256,12 @@ eshell-record-process-object
 
 (defun eshell-remove-process-entry (entry)
   "Record the process ENTRY as fully completed."
-  (if (and (eshell-processp (car entry))
-	   (cdr entry)
-	   eshell-done-messages-in-minibuffer)
-      (message "[%s]+ Done %s" (process-name (car entry))
-	       (process-command (car entry))))
+  (when (and eshell-subjob-messages
+             eshell-done-messages-in-minibuffer
+             (eshell-processp (car entry))
+             (cdr entry))
+    (message "[%s]+ Done %s" (process-name (car entry))
+             (process-command (car entry))))
   (setq eshell-process-list
 	(delq entry eshell-process-list)))
 
diff --git a/test/lisp/eshell/em-script-tests.el b/test/lisp/eshell/em-script-tests.el
index 74328844778..191755dcc3e 100644
--- a/test/lisp/eshell/em-script-tests.el
+++ b/test/lisp/eshell/em-script-tests.el
@@ -63,6 +63,19 @@ em-script-test/source-script/redirect/dev-null
         "\\`\\'"))
       (should (equal (buffer-string) "hibye")))))
 
+(ert-deftest em-script-test/source-script/background ()
+  "Test sourcing a script in the background."
+  (skip-unless (executable-find "echo"))
+  (ert-with-temp-file temp-file
+    :text "*echo hi"
+    (eshell-with-temp-buffer bufname "old"
+      (with-temp-eshell
+       (eshell-match-command-output
+        (format "source %s > #<%s> &" temp-file bufname)
+        "\\`\\'")
+       (eshell-wait-for-subprocess t))
+      (should (equal (buffer-string) "hi\n")))))
+
 (ert-deftest em-script-test/source-script/arg-vars ()
   "Test sourcing script with $0, $1, ... variables."
   (ert-with-temp-file temp-file :text "printnl $0 \"$1 $2\""
-- 
2.25.1

From 61da4551d45af5f66675c69de4851b30688c6ee0 Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Sat, 23 Sep 2023 11:36:11 -0700
Subject: [PATCH 2/2] Support Eshell iterative evaluation in the background

This really just generalizes Eshell's previous support for iterative
evaluation of a single current command to a list of multiple commands
(of which at most one can be in the foreground).

* lisp/eshell/esh-cmd.el (eshell-last-async-procs)
(eshell-current-command): Make obsolete in favor of...
(eshell-foreground-command): ... this
(eshell-background-commands): New variable.
(eshell-interactive-process-p, eshell-head-process)
(eshell-tail-process): Use 'eshell-foreground-command'.
(eshell-cmd-initialize): Initialize new variables.
(eshell-add-command, eshell-remove-command)
(eshell-commands-for-process): New functions.
(eshell-parse-command): Make 'eshell-do-subjob' the outermost call.
(eshell-do-subjob): Call 'eshell-resume-eval' to split this command
off from its parent forms.
(eshell-eval-command): Use 'eshell-add-command'.
(eshell-resume-command): Use 'eshell-commands-for-process'.
(eshell-resume-eval): Take a COMMAND argument.  Return
':eshell-background' form for deferred background commands.
(eshell-do-eval): Remove check for 'eshell-current-subjob-p'.  This is
handled differently now.

* lisp/eshell/em-smart.el (eshell-smart-display-move): Use
'eshell-foreground-command'.

* test/lisp/eshell/esh-cmd-tests.el
(esh-cmd-test/background/simple-command)
(esh-cmd-test/background/subcommand): New tests.
(esh-cmd-test/throw): Use 'eshell-foreground-command'.

* test/lisp/eshell/eshell-tests.el (eshell-test/queue-input): Use
'eshell-foreground-command'.

* test/lisp/eshell/em-script-tests.el
(em-script-test/source-script/background): Make the test script more
complex.

* doc/misc/eshell.texi (Bugs and ideas): Remove implemented feature.
---
 doc/misc/eshell.texi                |   2 -
 lisp/eshell/em-smart.el             |   2 +-
 lisp/eshell/esh-cmd.el              | 182 +++++++++++++++++++---------
 test/lisp/eshell/em-script-tests.el |   4 +-
 test/lisp/eshell/esh-cmd-tests.el   |  29 ++++-
 test/lisp/eshell/eshell-tests.el    |   2 +-
 6 files changed, 153 insertions(+), 68 deletions(-)

diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 8b3eb72aa66..cc94f610615 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -2568,8 +2568,6 @@ Bugs and ideas
 @samp{$=[REGEXP]}.  It indexes into the directory ring.
 @end table
 
-@item Eshell scripts can't execute in the background
-
 @item Support zsh's ``Parameter Expansion'' syntax, i.e., @samp{$@{@var{name}:-@var{val}@}}
 
 @item Create a mode @code{eshell-browse}
diff --git a/lisp/eshell/em-smart.el b/lisp/eshell/em-smart.el
index d5002a59d14..4c39a991ec6 100644
--- a/lisp/eshell/em-smart.el
+++ b/lisp/eshell/em-smart.el
@@ -294,7 +294,7 @@ eshell-smart-display-move
        ((eq this-command 'self-insert-command)
 	(if (eq last-command-event ? )
 	    (if (and eshell-smart-space-goes-to-end
-		     eshell-current-command)
+		     eshell-foreground-command)
 		(if (not (pos-visible-in-window-p (point-max)))
 		    (setq this-command 'scroll-up)
 		  (setq this-command 'eshell-smart-goto-end))
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index fc7d54a758d..762e65cf639 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -263,7 +263,24 @@ eshell-ensure-newline-p
 
 ;;; Internal Variables:
 
-(defvar eshell-current-command nil)
+;; These variables have been merged into `eshell-foreground-command'.
+;; Outside of this file, the most-common use for them is to check
+;; whether they're nil.
+(define-obsolete-variable-alias 'eshell-last-async-procs
+  'eshell-foreground-command "30.1")
+(define-obsolete-variable-alias 'eshell-current-command
+  'eshell-foreground-command "30.1")
+
+(defvar eshell-foreground-command nil
+  "The currently-running foreground command, if any.
+This is a list of the form (COMMAND PROCESSES).  COMMAND is the
+Eshell command form.  PROCESSES is a list of processes that
+deferred the command.")
+(defvar eshell-background-commands nil
+  "A list of currently-running deferred commands.
+Each element is of the form (COMMAND PROCESSES), as with
+`eshell-foreground-command' (which see).")
+
 (defvar eshell-command-name nil)
 (defvar eshell-command-arguments nil)
 (defvar eshell-in-pipeline-p nil
@@ -273,11 +290,6 @@ eshell-in-pipeline-p
 (defvar eshell-in-subcommand-p nil)
 (defvar eshell-last-arguments nil)
 (defvar eshell-last-command-name nil)
-(defvar eshell-last-async-procs nil
-  "The currently-running foreground process(es).
-When executing a pipeline, this is a list of all the pipeline's
-processes, with the first usually reading from stdin and last
-usually writing to stdout.")
 
 (defvar eshell-allow-commands t
   "If non-nil, allow evaluating command forms (including Lisp forms).
@@ -294,29 +306,29 @@ 'eshell-commands-forbidden
 
 (defsubst eshell-interactive-process-p ()
   "Return non-nil if there is a currently running command process."
-  eshell-last-async-procs)
+  eshell-foreground-command)
 
 (defsubst eshell-head-process ()
   "Return the currently running process at the head of any pipeline.
 This only returns external (non-Lisp) processes."
-  (car eshell-last-async-procs))
+  (caadr eshell-foreground-command))
 
 (defsubst eshell-tail-process ()
   "Return the currently running process at the tail of any pipeline.
 This only returns external (non-Lisp) processes."
-  (car (last eshell-last-async-procs)))
+  (car (last (cadr eshell-foreground-command))))
 
 (define-obsolete-function-alias 'eshell-interactive-process
   'eshell-tail-process "29.1")
 
 (defun eshell-cmd-initialize ()     ;Called from `eshell-mode' via intern-soft!
   "Initialize the Eshell command processing module."
-  (setq-local eshell-current-command nil)
+  (setq-local eshell-foreground-command nil)
+  (setq-local eshell-background-commands nil)
   (setq-local eshell-command-name nil)
   (setq-local eshell-command-arguments nil)
   (setq-local eshell-last-arguments nil)
   (setq-local eshell-last-command-name nil)
-  (setq-local eshell-last-async-procs nil)
 
   (add-hook 'eshell-kill-hook #'eshell-resume-command nil t)
   (add-hook 'eshell-parse-argument-hook
@@ -337,6 +349,47 @@ eshell-complete-lisp-symbols
       (throw 'pcomplete-completions
 	     (all-completions pcomplete-stub obarray 'boundp)))))
 
+;; Deferred command management
+
+(defun eshell-add-command (form &optional background)
+  "Add a command FORM to our list of known commands and return the new entry.
+If non-nil, BACKGROUND indicates that this is a command running
+in the background.  The result is a command entry in the
+form (BACKGROUND FORM PROCESSES), where PROCESSES is initially
+nil."
+  (cons background
+        (if background
+            (car (push (list form nil) eshell-background-commands))
+          (cl-assert (null eshell-foreground-command))
+          (setq eshell-foreground-command (list form nil)))))
+
+(defun eshell-remove-command (command)
+  "Add COMMAND to our list of known commands.
+If non-nil, BACKGROUND indicates that this is a command running
+in the background."
+  (let ((background (car command))
+        (entry (cdr command)))
+    (if background
+        (setq eshell-background-commands
+              (delq entry eshell-background-commands))
+      (cl-assert (eq eshell-foreground-command entry))
+      (setq eshell-foreground-command nil))))
+
+(defun eshell-commands-for-process (process)
+  "Return all commands associated with a PROCESS.
+Each element will have the form (COMMAND PROCESSES BACKGROUND),
+as with the arguments to `eshell-push-command' (which see).
+
+Usually, there should only be one element in this list, but it's
+theoretically possible to have more than one associated command
+for a given process."
+  (nconc (when (memq process (cadr eshell-foreground-command))
+           (list (cons nil eshell-foreground-command)))
+         (seq-keep (lambda (cmd)
+                     (when (memq process (cadr cmd))
+                       (cons 'background cmd)))
+                   eshell-background-commands)))
+
 ;; Command parsing
 
 (defsubst eshell--region-p (object)
@@ -407,8 +460,6 @@ eshell-parse-command
        (lambda (cmd)
          (let ((sep (pop sep-terms)))
            (setq cmd (eshell-parse-pipeline cmd))
-           (when (equal sep "&")
-             (setq cmd `(eshell-do-subjob (cons :eshell-background ,cmd))))
            (unless eshell-in-pipeline-p
              (setq cmd `(eshell-trap-errors ,cmd)))
            ;; Copy I/O handles so each full statement can manipulate
@@ -416,6 +467,8 @@ eshell-parse-command
            ;; command in the list; we won't use the originals again
            ;; anyway.
            (setq cmd `(eshell-with-copied-handles ,cmd ,(not sep)))
+           (when (equal sep "&")
+             (setq cmd `(eshell-do-subjob ,cmd)))
            cmd))
        sub-chains)))
     (if toplevel
@@ -740,13 +793,13 @@ eshell-separate-commands
 
 (defmacro eshell-do-subjob (object)
   "Evaluate a command OBJECT as a subjob.
-We indicate that the process was run in the background by returning it
-ensconced in a list."
+We indicate that the process was run in the background by
+returning it as (:eshell-background . PROCS)."
   `(let ((eshell-current-subjob-p t)
          ;; Print subjob messages.  This could have been cleared
          ;; (e.g. by `eshell-source-file', which see).
          (eshell-subjob-messages t))
-     ,object))
+     (eshell-resume-eval (eshell-add-command ',object 'background))))
 
 (defmacro eshell-commands (object &optional silent)
   "Place a valid set of handles, and context, around command OBJECT."
@@ -980,12 +1033,12 @@ eshell-eval-command
 COMMAND, if any.  If COMMAND is a background command, return the
 process(es) in a cons cell like:
 
-  (:eshell-background . PROCESS)"
-  (if eshell-current-command
+  (:eshell-background . PROCS)"
+  (if eshell-foreground-command
       (progn
         ;; We can just stick the new command at the end of the current
         ;; one, and everything will happen as it should.
-        (setcdr (last (cdr eshell-current-command))
+        (setcdr (last (cdar eshell-foreground-command))
                 (list `(let ((here (and (eobp) (point))))
                          ,(and input
                                `(insert-and-inherit ,(concat input "\n")))
@@ -994,56 +1047,66 @@ eshell-eval-command
                          (eshell-do-eval ',command))))
         (eshell-debug-command 'form
           "enqueued command form for %S\n\n%s"
-          (or input "<no string>") (eshell-stringify eshell-current-command)))
+          (or input "<no string>")
+          (eshell-stringify (car eshell-foreground-command))))
     (eshell-debug-command-start input)
-    (setq eshell-current-command command)
     (let* (result
            (delim (catch 'eshell-incomplete
-                    (ignore (setq result (eshell-resume-eval))))))
+                    (ignore (setq result (eshell-resume-eval
+                                          (eshell-add-command command)))))))
       (when delim
         (error "Unmatched delimiter: %S" delim))
       result)))
 
 (defun eshell-resume-command (proc status)
-  "Resume the current command when a pipeline ends."
-  (when (and proc
-             ;; Make sure PROC is one of our foreground processes and
-             ;; that all of those processes are now dead.
-             (member proc eshell-last-async-procs)
-             (not (seq-some #'eshell-process-active-p eshell-last-async-procs)))
-    (if (and ;; Check STATUS to determine whether we want to resume or
-             ;; abort the command.
-             (stringp status)
-             (not (string= "stopped" status))
-             (not (string-match eshell-reset-signals status)))
-        (eshell-resume-eval)
-      (setq eshell-last-async-procs nil)
-      (setq eshell-current-command nil)
-      (declare-function eshell-reset "esh-mode" (&optional no-hooks))
-      (eshell-reset))))
-
-(defun eshell-resume-eval ()
-  "Destructively evaluate a form which may need to be deferred."
-  (setq eshell-last-async-procs nil)
-  (when eshell-current-command
-    (eshell-condition-case err
-        (let (retval procs)
-          (unwind-protect
-              (progn
-                (setq procs (catch 'eshell-defer
-                              (ignore (setq retval
-                                            (eshell-do-eval
-                                             eshell-current-command)))))
-                (when retval
-                  (cadr retval)))
-            (setq eshell-last-async-procs procs)
+  "Resume the current command when a pipeline ends.
+PROC is the process that invoked this from its sentinel, and
+STATUS is its status."
+  (when proc
+    (dolist (command (eshell-commands-for-process proc))
+      (unless (seq-some #'eshell-process-active-p (nth 2 command))
+        ;; Remove this command from `eshell-foreground-command' or
+        ;; `eshell-background-commands'.  If it gets deferred again,
+        ;; `eshell-resume-eval' will re-add it.
+        ;; (eshell-pop-command cmd-info)
+        (setf (nth 2 command) nil) ; Clear processes from command.
+        (if (and ;; Check STATUS to determine whether we want to resume or
+                 ;; abort the command.
+                 (stringp status)
+                 (not (string= "stopped" status))
+                 (not (string-match eshell-reset-signals status)))
+            (eshell-resume-eval command)
+          (eshell-remove-command command)
+          (declare-function eshell-reset "esh-mode" (&optional no-hooks))
+          (eshell-reset))))))
+
+(defun eshell-resume-eval (command)
+  "Destructively evaluate a COMMAND which may need to be deferred.
+COMMAND is a command entry of the form (BACKGROUND FORM
+PROCESSES) (see `eshell-add-command').
+
+Return the result of COMMAND if it wasn't deferred.  If
+BACKGROUND is non-nil and Eshell defers COMMAND, return a list of
+the form (:eshell-background . PROCS)."
+  (eshell-condition-case err
+      (let (retval procs)
+        (unwind-protect
+            (progn
+              (setq procs
+                    (catch 'eshell-defer
+                      (ignore (setq retval (eshell-do-eval (cadr command))))))
+              (cond
+               (retval (cadr retval))
+               ((car command) (cons :eshell-background procs))))
+          (if procs
+              (setf (nth 2 command) procs)
             ;; If we didn't defer this command, clear it out.  This
             ;; applies both when the command has finished normally,
-            ;; and when a signal or thrown value causes us to unwind.
-            (unless procs
-              (setq eshell-current-command nil))))
-      (error
-       (error (error-message-string err))))))
+            ;; and when a signal or thrown value causes us to
+            ;; unwind.
+            (eshell-remove-command command))))
+    (error
+     (error (error-message-string err)))))
 
 (defmacro eshell-manipulate (form tag &rest body)
   "Manipulate a command FORM with BODY, using TAG as a debug identifier."
@@ -1272,7 +1335,6 @@ eshell-do-eval
 		    (setcdr form (cdr new-form)))
 		  (eshell-do-eval form synchronous-p))
               (if-let (((memq (car form) eshell-deferrable-commands))
-                       ((not eshell-current-subjob-p))
                        (procs (eshell-make-process-list result)))
                   (if synchronous-p
 		      (apply #'eshell/wait procs)
diff --git a/test/lisp/eshell/em-script-tests.el b/test/lisp/eshell/em-script-tests.el
index 191755dcc3e..02e4125d827 100644
--- a/test/lisp/eshell/em-script-tests.el
+++ b/test/lisp/eshell/em-script-tests.el
@@ -67,14 +67,14 @@ em-script-test/source-script/background
   "Test sourcing a script in the background."
   (skip-unless (executable-find "echo"))
   (ert-with-temp-file temp-file
-    :text "*echo hi"
+    :text "*echo hi\nif {[ foo = foo ]} {*echo bye}"
     (eshell-with-temp-buffer bufname "old"
       (with-temp-eshell
        (eshell-match-command-output
         (format "source %s > #<%s> &" temp-file bufname)
         "\\`\\'")
        (eshell-wait-for-subprocess t))
-      (should (equal (buffer-string) "hi\n")))))
+      (should (equal (buffer-string) "hi\nbye\n")))))
 
 (ert-deftest em-script-test/source-script/arg-vars ()
   "Test sourcing script with $0, $1, ... variables."
diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el
index 643038f89ff..e0783b26ad6 100644
--- a/test/lisp/eshell/esh-cmd-tests.el
+++ b/test/lisp/eshell/esh-cmd-tests.el
@@ -103,6 +103,32 @@ esh-cmd-test/let-rebinds-after-defer
             "}")
     "value\nexternal\nvalue\n")))
 
+
+;; Background command invocation
+
+(ert-deftest esh-cmd-test/background/simple-command ()
+  "Test invocation with a simple background command."
+  (skip-unless (executable-find "echo"))
+  (eshell-with-temp-buffer bufname ""
+    (with-temp-eshell
+     (eshell-match-command-output
+      (format "*echo hi > #<%s> &" bufname)
+      (rx "[echo" (? ".exe") "] " (+ digit) "\n"))
+     (eshell-wait-for-subprocess t))
+    (should (equal (buffer-string) "hi\n"))))
+
+(ert-deftest esh-cmd-test/background/subcommand ()
+  "Test invocation with a background command containing subcommands."
+  (skip-unless (and (executable-find "echo")
+                    (executable-find "rev")))
+  (eshell-with-temp-buffer bufname ""
+    (with-temp-eshell
+     (eshell-match-command-output
+      (format "*echo ${*echo hello | rev} > #<%s> &" bufname)
+      (rx "[echo" (? ".exe") "] " (+ digit) "\n"))
+     (eshell-wait-for-subprocess t))
+    (should (equal (buffer-string) "olleh\n"))))
+
 
 ;; Lisp forms
 
@@ -453,8 +479,7 @@ esh-cmd-test/throw
                  "echo hi; (throw 'tag 42); echo bye"))
               42))
    (should (eshell-match-output "\\`hi\n\\'"))
-   (should-not eshell-current-command)
-   (should-not eshell-last-async-procs)
+   (should-not eshell-foreground-command)
    ;; Make sure we can call another command after throwing.
    (eshell-match-command-output "echo again" "\\`again\n")))
 
diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el
index 25c8cfd389c..5b2517c4ca5 100644
--- a/test/lisp/eshell/eshell-tests.el
+++ b/test/lisp/eshell/eshell-tests.el
@@ -132,7 +132,7 @@ eshell-test/queue-input
    (eshell-insert-command "sleep 1; echo slept")
    (eshell-insert-command "echo alpha" #'eshell-queue-input)
    (let ((start (marker-position (eshell-beginning-of-output))))
-     (eshell-wait-for (lambda () (not eshell-current-command)))
+     (eshell-wait-for (lambda () (not eshell-foreground-command)))
      (should (string-match "^slept\n.*echo alpha\nalpha\n$"
                            (buffer-substring-no-properties
                             start (eshell-end-of-output)))))))
-- 
2.25.1