Subject: bug#65590: 29.0.50; esh-var-test/interp-concat-cmd fails on macOS

From: Jim Porter <[email-masked]>
Date: Tue, 29 Aug 2023 17:44:28 -0700

On 8/29/2023 11:59 AM, Stefan Kangas wrote:
> Jim Porter <[email-masked]> writes:
> 
>> This looks like a race condition when executing this command:
>>
>>     echo ${*echo "foo\nbar"}-baz
>>
>> That *should* print "foo\nbar-baz", but it tries to print "-baz" (and
>> fails). That appears to be because the subcommand (*echo "foo\nbar")
>> produces no output.
> 
> I can confirm that the error is intermittent, as I'm not seeing it on a re-run.

Can you try the following patches? They shouldn't change the behavior, 
but they do add some extra debugging information to failed tests.

If you can get this to occur when using Eshell interactively, you can 
also enable this output by calling "eshell-debug process" at an Eshell 
prompt before running the offending command.

From 62019d9ff4b8af07bbfb8294751811dcad79ed99 Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Tue, 29 Aug 2023 17:02:40 -0700
Subject: [PATCH 1/2] Fix handling of Eshell debug modes

Previously, these were enabled/disabled at byte-compilation time, but
we want to control them at runtime.

* lisp/eshell/esh-cmd.el (eshell-eval-command): Call
'eshell-debug-command-start'.
(eshell-manipulate): Check 'eshell-debug-command' at runtime.  Update
callers.
(eshell-debug-command): Move to "esh-util.el".
(eshell/eshell-debug, pcomplate/eshell-mode/eshell-debug): Move to
"em-basic.el".
(eshell-debug-show-parsed-args): Update implementation.

* lisp/eshell/esh-util.el (eshell-debug-command): Move from
"esh-cmd.el" and convert to a list.
(eshell-debug-command-buffer): New variable.
(eshell-condition-case): Check 'eshell-handle-errors' at runtime.
(eshell-debug-command-start): New function.
(eshell-debug-command): Move from "esh-cmd.el" and convert to a macro.

* lisp/eshell/em-basic.el (eshell/eshell-debug)
(pcomplete/eshell-mode/eshell-debug): Move from "esh-cmd.el" and
reimplement.

* lisp/eshell/eshell.el (eshell-command): Pass the original input to
'eshell-eval-command'.

* doc/misc/eshell.texi (Built-ins): Update documentation for
'eshell-debug'.
---
 doc/misc/eshell.texi    |  21 ++++++--
 lisp/eshell/em-basic.el |  32 ++++++++++++
 lisp/eshell/esh-cmd.el  | 105 +++++++++-------------------------------
 lisp/eshell/esh-util.el |  44 +++++++++++++++--
 lisp/eshell/eshell.el   |   3 +-
 5 files changed, 113 insertions(+), 92 deletions(-)

diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index f8f60bae13a..09aafdcf070 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -619,10 +619,23 @@ Built-ins
 @item eshell-debug
 @cmindex eshell-debug
 Toggle debugging information for Eshell itself.  You can pass this
-command the argument @code{errors} to enable/disable Eshell trapping
-errors when evaluating commands, or the argument @code{commands} to
-show/hide command execution progress in the buffer @code{*eshell last
-cmd*}.
+command one or more of the following arguments:
+
+@itemize @bullet
+
+@item
+@code{error}, to enable/disable Eshell trapping errors when evaluating
+commands;
+
+@item
+@code{form}, to show/hide Eshell command form manipulation in the
+buffer @code{*eshell last cmd*}; or
+
+@item
+@code{process}, to show/hide external process events in the buffer
+@code{*eshell last cmd*}.
+
+@end itemize
 
 @item exit
 @cmindex exit
diff --git a/lisp/eshell/em-basic.el b/lisp/eshell/em-basic.el
index 016afe811b2..cea4b78129f 100644
--- a/lisp/eshell/em-basic.el
+++ b/lisp/eshell/em-basic.el
@@ -188,6 +188,38 @@ eshell/umask
 
 (put 'eshell/umask 'eshell-no-numeric-conversions t)
 
+(defun eshell/eshell-debug (&rest args)
+  "A command for toggling certain debug variables."
+  (eshell-eval-using-options
+   "eshell-debug" args
+   '((?h "help" nil nil "display this usage message")
+     :usage "[kinds]...
+This command is used to aid in debugging problems related to Eshell
+itself.  It is not useful for anything else.  The recognized `kinds'
+are:
+
+   error       stops Eshell from trapping errors
+   form        shows command form manipulation in `*eshell last cmd*'
+   process     shows process events in `*eshell last cmd*'")
+   (if args
+       (dolist (kind args)
+         (if (equal kind "error")
+             (setq eshell-handle-errors (not eshell-handle-errors))
+           (let ((kind-sym (intern kind)))
+             (if (memq kind-sym eshell-debug-command)
+                 (setq eshell-debug-command
+                       (delq kind-sym eshell-debug-command))
+               (push kind-sym eshell-debug-command)))))
+     ;; Output the currently-enabled debug kinds.
+     (unless eshell-handle-errors
+       (eshell-print "error\n"))
+     (dolist (kind eshell-debug-command)
+       (eshell-printn (symbol-name kind))))))
+
+(defun pcomplete/eshell-mode/eshell-debug ()
+  "Completion for the `debug' command."
+  (while (pcomplete-here '("error" "form" "process"))))
+
 (provide 'em-basic)
 
 ;; Local Variables:
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 80066263396..ed2d6c71fc8 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -237,17 +237,6 @@ eshell-cmd-load-hook
   :version "24.1"		       ; removed eshell-cmd-initialize
   :type 'hook)
 
-(defcustom eshell-debug-command nil
-  "If non-nil, enable Eshell debugging code.
-This is slow, and only useful for debugging problems with Eshell.
-If you change this without using customize after Eshell has loaded,
-you must re-load `esh-cmd.el'."
-  :initialize 'custom-initialize-default
-  :set (lambda (symbol value)
-	 (set symbol value)
-	 (load "esh-cmd"))
-  :type 'boolean)
-
 (defcustom eshell-deferrable-commands
   '(eshell-named-command
     eshell-lisp-command
@@ -436,22 +425,9 @@ eshell-parse-command
                             (run-hooks 'eshell-post-command-hook)))
       (macroexp-progn commands))))
 
-(defun eshell-debug-command (tag subform)
-  "Output a debugging message to `*eshell last cmd*'."
-  (let ((buf (get-buffer-create "*eshell last cmd*"))
-	(text (eshell-stringify eshell-current-command)))
-    (with-current-buffer buf
-      (if (not tag)
-	  (erase-buffer)
-	(insert "\n\C-l\n" tag "\n\n" text
-		(if subform
-		    (concat "\n\n" (eshell-stringify subform)) ""))))))
-
 (defun eshell-debug-show-parsed-args (terms)
   "Display parsed arguments in the debug buffer."
-  (ignore
-   (if eshell-debug-command
-       (eshell-debug-command "parsed arguments" terms))))
+  (ignore (eshell-debug-command 'form "parsed arguments" terms)))
 
 (defun eshell-no-command-conversion (terms)
   "Don't convert the command argument."
@@ -942,38 +918,6 @@ eshell-command-to-value
 ;; finishes, it will resume the evaluation using the remainder of the
 ;; command tree.
 
-(defun eshell/eshell-debug (&rest args)
-  "A command for toggling certain debug variables."
-  (ignore
-   (cond
-    ((not args)
-     (if eshell-handle-errors
-	 (eshell-print "errors\n"))
-     (if eshell-debug-command
-	 (eshell-print "commands\n")))
-    ((member (car args) '("-h" "--help"))
-     (eshell-print "usage: eshell-debug [kinds]
-
-This command is used to aid in debugging problems related to Eshell
-itself.  It is not useful for anything else.  The recognized `kinds'
-at the moment are:
-
-  errors       stops Eshell from trapping errors
-  commands     shows command execution progress in `*eshell last cmd*'
-"))
-    (t
-     (while args
-       (cond
-	((string= (car args) "errors")
-	 (setq eshell-handle-errors (not eshell-handle-errors)))
-	((string= (car args) "commands")
-	 (setq eshell-debug-command (not eshell-debug-command))))
-       (setq args (cdr args)))))))
-
-(defun pcomplete/eshell-mode/eshell-debug ()
-  "Completion for the `debug' command."
-  (while (pcomplete-here '("errors" "commands"))))
-
 (iter-defun eshell--find-subcommands (haystack)
   "Recursively search for subcommand forms in HAYSTACK.
 This yields the SUBCOMMANDs when found in forms like
@@ -1049,10 +993,7 @@ eshell-eval-command
                        (if here
                            (eshell-update-markers here))
                        (eshell-do-eval ',command))))
-    (and eshell-debug-command
-         (with-current-buffer (get-buffer-create "*eshell last cmd*")
-           (erase-buffer)
-           (insert "command: \"" input "\"\n")))
+    (eshell-debug-command-start input)
     (setq eshell-current-command command)
     (let* (result
            (delim (catch 'eshell-incomplete
@@ -1088,17 +1029,17 @@ eshell-resume-eval
     (error
      (error (error-message-string err)))))
 
-(defmacro eshell-manipulate (tag &rest commands)
-  "Manipulate a COMMAND form, with TAG as a debug identifier."
-  (declare (indent 1))
-  ;; Check `bound'ness since at compile time the code until here has not
-  ;; executed yet.
-  (if (not (and (boundp 'eshell-debug-command) eshell-debug-command))
-      `(progn ,@commands)
-    `(progn
-       (eshell-debug-command ,(eval tag) form)
-       ,@commands
-       (eshell-debug-command ,(concat "done " (eval tag)) form))))
+(defmacro eshell-manipulate (form tag &rest body)
+  "Manipulate a command FORM with BODY, using TAG as a debug identifier."
+  (declare (indent 2))
+  (let ((tag-symbol (make-symbol "tag")))
+    `(if (not (memq 'form eshell-debug-command))
+         (progn ,@body)
+       (let ((,tag-symbol ,tag))
+         (eshell-debug-command 'form ,tag-symbol ,form 'always)
+         ,@body
+         (eshell-debug-command 'form (concat "done " ,tag-symbol) ,form
+                               'always)))))
 
 (defun eshell-do-eval (form &optional synchronous-p)
   "Evaluate FORM, simplifying it as we go.
@@ -1125,8 +1066,8 @@ eshell-do-eval
     ;; we can modify any `let' forms to evaluate only once.
     (if (macrop (car form))
         (let ((exp (copy-tree (macroexpand form))))
-	  (eshell-manipulate (format-message "expanding macro `%s'"
-					     (symbol-name (car form)))
+          (eshell-manipulate form
+              (format-message "expanding macro `%s'" (symbol-name (car form)))
 	    (setcar form (car exp))
 	    (setcdr form (cdr exp)))))
     (let ((args (cdr form)))
@@ -1138,7 +1079,7 @@ eshell-do-eval
         (let ((new-form (copy-tree `(let ((eshell--command-body nil)
                                           (eshell--test-body nil))
                                       (eshell--wrapped-while ,@args)))))
-          (eshell-manipulate "modifying while form"
+          (eshell-manipulate form "modifying while form"
             (setcar form (car new-form))
             (setcdr form (cdr new-form)))
           (eshell-do-eval form synchronous-p)))
@@ -1161,7 +1102,7 @@ eshell-do-eval
           (setq eshell--command-body nil
                 eshell--test-body (copy-tree (car args)))))
        ((eq (car form) 'if)
-        (eshell-manipulate "evaluating if condition"
+        (eshell-manipulate form "evaluating if condition"
           (setcar args (eshell-do-eval (car args) synchronous-p)))
         (eshell-do-eval
          (cond
@@ -1180,7 +1121,7 @@ eshell-do-eval
 	(eval form))
        ((eq (car form) 'let)
         (unless (eq (car-safe (cadr args)) 'eshell-do-eval)
-          (eshell-manipulate "evaluating let args"
+          (eshell-manipulate form "evaluating let args"
             (dolist (letarg (car args))
               (when (and (listp letarg)
                          (not (eq (cadr letarg) 'quote)))
@@ -1207,7 +1148,7 @@ eshell-do-eval
             ;; the let-bindings' values so that those values are
             ;; correct when we resume evaluation of this form.
             (when deferred
-              (eshell-manipulate "rebinding let args after `eshell-defer'"
+              (eshell-manipulate form "rebinding let args after `eshell-defer'"
                 (let ((bindings (car args)))
                   (while bindings
                     (let ((binding (if (consp (car bindings))
@@ -1232,7 +1173,7 @@ eshell-do-eval
 	(unless (eq (car form) 'unwind-protect)
 	  (setq args (cdr args)))
 	(unless (eq (caar args) 'eshell-do-eval)
-	  (eshell-manipulate "handling special form"
+          (eshell-manipulate form "handling special form"
 	    (setcar args `(eshell-do-eval ',(car args) ,synchronous-p))))
 	(eval form))
        ((eq (car form) 'setq)
@@ -1242,7 +1183,7 @@ eshell-do-eval
 	(list 'quote (eval form)))
        (t
 	(if (and args (not (memq (car form) '(run-hooks))))
-	    (eshell-manipulate
+            (eshell-manipulate form
 		(format-message "evaluating arguments to `%s'"
 				(symbol-name (car form)))
 	      (while args
@@ -1283,7 +1224,7 @@ eshell-do-eval
                      (setq result (eval form))))))
 	    (if new-form
 		(progn
-		  (eshell-manipulate "substituting replacement form"
+                  (eshell-manipulate form "substituting replacement form"
 		    (setcar form (car new-form))
 		    (setcdr form (cdr new-form)))
 		  (eshell-do-eval form synchronous-p))
@@ -1292,7 +1233,7 @@ eshell-do-eval
                        (procs (eshell-make-process-pair result)))
                   (if synchronous-p
 		      (eshell/wait (cdr procs))
-		    (eshell-manipulate "inserting ignore form"
+                    (eshell-manipulate form "inserting ignore form"
 		      (setcar form 'ignore)
 		      (setcdr form nil))
 		    (throw 'eshell-defer procs))
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index 87cd1f5dcb2..3a318056445 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -102,6 +102,15 @@ eshell-ange-ls-uids
 				     (string :tag "Username")
 				     (repeat :tag "UIDs" string))))))
 
+(defcustom eshell-debug-command nil
+  "A list of debug features to enable when running Eshell commands.
+Possible entries are `form', to log the manipulation of Eshell
+command forms, and `io', to log I/O operations.
+
+If nil, don't debug commands at all."
+  :version "30.1"
+  :type '(set (const :tag "Form manipulation" form)))
+
 ;;; Internal Variables:
 
 (defvar eshell-number-regexp
@@ -145,6 +154,9 @@ eshell-command-output-properties
                             ,#'eshell--mark-yanked-as-output))
   "A list of text properties to apply to command output.")
 
+(defvar eshell-debug-command-buffer "*eshell last cmd*"
+  "The name of the buffer to log debug messages about command invocation.")
+
 ;;; Obsolete variables:
 
 (define-obsolete-variable-alias 'eshell-host-names
@@ -164,11 +176,33 @@ eshell-condition-case
   "If `eshell-handle-errors' is non-nil, this is `condition-case'.
 Otherwise, evaluates FORM with no error handling."
   (declare (indent 2) (debug (sexp form &rest form)))
-  (if eshell-handle-errors
-      `(condition-case-unless-debug ,tag
-	   ,form
-	 ,@handlers)
-    form))
+  `(if eshell-handle-errors
+       (condition-case-unless-debug ,tag
+           ,form
+         ,@handlers)
+     ,form))
+
+(defun eshell-debug-command-start (command)
+  "Start debugging output for the command string COMMAND.
+If debugging is enabled (see `eshell-debug-command'), this will
+start logging to `*eshell last cmd*'."
+  (when eshell-debug-command
+    (with-current-buffer (get-buffer-create eshell-debug-command-buffer)
+      (erase-buffer)
+      (insert "command: \"" command "\"\n"))))
+
+(defmacro eshell-debug-command (kind message &optional form always)
+  "Output a debugging message to `*eshell last cmd*' if debugging is enabled.
+KIND is the kind of message to log (either `form' or `io').  If
+present in `eshell-debug-command' (or if ALWAYS is non-nil),
+output this message; otherwise, ignore it."
+  (let ((kind-sym (make-symbol "kind")))
+    `(let ((,kind-sym ,kind))
+       (when ,(or always `(memq ,kind-sym eshell-debug-command))
+         (with-current-buffer (get-buffer-create eshell-debug-command-buffer)
+           (insert "\n\C-l\n[" (symbol-name ,kind-sym) "] " ,message)
+           (when-let ((form ,form))
+             (insert "\n\n" (eshell-stringify form))))))))
 
 (defun eshell--mark-as-output (start end &optional object)
   "Mark the text from START to END as Eshell output.
diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el
index 15fc2ae6310..cbd0de3c093 100644
--- a/lisp/eshell/eshell.el
+++ b/lisp/eshell/eshell.el
@@ -301,7 +301,8 @@ eshell-command
                     `(let ((eshell-current-handles
                             (eshell-create-handles ,stdout 'insert))
                            (eshell-current-subjob-p))
-		       ,(eshell-parse-command command))))
+		       ,(eshell-parse-command command))
+                    command))
 	     intr
 	     (bufname (if (eq (car-safe proc) :eshell-background)
 			  "*Eshell Async Command Output*"
-- 
2.25.1

From b8ca3d6d8ee1b1a66d0c8dac88e2cebce43722ac Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Tue, 29 Aug 2023 17:11:42 -0700
Subject: [PATCH 2/2] Add debug instrumentation for Eshell process management

* lisp/eshell/esh-util.el (eshell-debug-command): Add 'process' type.

* lisp/eshell/esh-proc.el (eshell-gather-process-output)
(eshell-interactive-process-filter, eshell-insertion-filter)
(eshell-sentinel): Call 'eshell-debug-command'.

* test/lisp/eshell/eshell-tests-helpers.el (with-temp-eshell): Add
'process' to 'eshell-debug'command'
(eshell-get-debug-logs): New function...
(eshell-match-command-output, eshell-command-result-equal): ... use
it.
---
 lisp/eshell/esh-proc.el                  | 17 ++++++++++++-
 lisp/eshell/esh-util.el                  |  3 ++-
 test/lisp/eshell/eshell-tests-helpers.el | 31 ++++++++++++++++++------
 3 files changed, 41 insertions(+), 10 deletions(-)

diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 9c4036004ff..87719c019cb 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -332,6 +332,8 @@ eshell-gather-process-output
                :connection-type conn-type
                :stderr stderr-proc
                :file-handler t)))
+      (eshell-debug-command
+       'process (format-message "started external process `%s'" proc))
       (eshell-record-process-object proc)
       (eshell-record-process-properties proc)
       (run-hook-with-args 'eshell-exec-hook proc)
@@ -410,6 +412,9 @@ eshell-interactive-process-filter
   "Send the output from PROCESS (STRING) to the interactive display.
 This is done after all necessary filtering has been done."
   (when string
+    (eshell-debug-command
+     'process (format-message "received output from process `%s'\n\n%s"
+                              process string))
     (eshell--mark-as-output 0 (length string) string)
     (require 'esh-mode)
     (declare-function eshell-interactive-filter "esh-mode" (buffer string))
@@ -436,6 +441,9 @@ eshell-insertion-filter
                 (data (process-get proc :eshell-pending)))
             (process-put proc :eshell-pending nil)
             (process-put proc :eshell-busy t)
+            (eshell-debug-command
+             'process (format-message "received output from process `%s'\n\n%s"
+                                      proc string))
             (unwind-protect
                 (condition-case nil
                     (eshell-output-object data index handles)
@@ -460,6 +468,9 @@ eshell-insertion-filter
 (defun eshell-sentinel (proc string)
   "Generic sentinel for command processes.  Reports only signals.
 PROC is the process that's exiting.  STRING is the exit message."
+  (eshell-debug-command
+   'process (format-message "sentinel for external process `%s': %S"
+                            proc string))
   (when (buffer-live-p (process-buffer proc))
     (with-current-buffer (process-buffer proc)
       (unwind-protect
@@ -492,7 +503,11 @@ eshell-sentinel
                              status
                              (when status (list 'quote (= status 0)))
                              handles)
-                            (eshell-kill-process-function proc string)))))
+                            (eshell-kill-process-function proc string)
+                            (eshell-debug-command
+                             'process
+                             (format-message
+                              "finished external process `%s'" proc))))))
                 (funcall finish-io))))
         (when-let ((entry (assq proc eshell-process-list)))
           (eshell-remove-process-entry entry))))))
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index 3a318056445..5f7ac82ae57 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -109,7 +109,8 @@ eshell-debug-command
 
 If nil, don't debug commands at all."
   :version "30.1"
-  :type '(set (const :tag "Form manipulation" form)))
+  :type '(set (const :tag "Form manipulation" form)
+              (const :tag "Process operations" process)))
 
 ;;; Internal Variables:
 
diff --git a/test/lisp/eshell/eshell-tests-helpers.el b/test/lisp/eshell/eshell-tests-helpers.el
index 2c913d71cb4..e300f3d657a 100644
--- a/test/lisp/eshell/eshell-tests-helpers.el
+++ b/test/lisp/eshell/eshell-tests-helpers.el
@@ -54,6 +54,12 @@ with-temp-eshell
        (let* (;; We want no history file, so prevent Eshell from falling
               ;; back on $HISTFILE.
               (process-environment (cons "HISTFILE" process-environment))
+              ;; Enable process debug instrumentation.  We may be able
+              ;; to remove this eventually once we're confident that
+              ;; all the process bugs have been worked out.  (At that
+              ;; point, we can just enable this selectively when
+              ;; needed.)
+              (eshell-debug-command (cons 'process eshell-debug-command))
               (eshell-history-file-name nil)
               (eshell-last-dir-ring-file-name nil)
               (eshell-buffer (eshell t)))
@@ -96,6 +102,13 @@ eshell-wait-for-subprocess
    (lambda ()
      (not (if all eshell-process-list (eshell-interactive-process-p))))))
 
+(defun eshell-get-debug-logs ()
+  "Get debug command logs for displaying on test failures."
+  (when (get-buffer eshell-debug-command-buffer)
+    (let ((separator (make-string 40 ?-)))
+      (with-current-buffer eshell-debug-command-buffer
+        (string-replace "\f" separator (buffer-string))))))
+
 (defun eshell-insert-command (command &optional func)
   "Insert a COMMAND at the end of the buffer.
 After inserting, call FUNC.  If FUNC is nil, instead call
@@ -135,10 +148,11 @@ eshell-match-command-output
 
 If IGNORE-ERRORS is non-nil, ignore any errors signaled when
 inserting the command."
-  (let ((debug-on-error (and (not ignore-errors) debug-on-error)))
-    (eshell-insert-command command func))
-  (eshell-wait-for-subprocess)
-  (should (eshell-match-output regexp)))
+  (ert-info (#'eshell-get-debug-logs :prefix "Command logs: ")
+    (let ((debug-on-error (and (not ignore-errors) debug-on-error)))
+      (eshell-insert-command command func))
+    (eshell-wait-for-subprocess)
+    (should (eshell-match-output regexp))))
 
 (defvar eshell-history-file-name)
 
@@ -164,10 +178,11 @@ eshell-command-result--equal-explainer
 
 (defun eshell-command-result-equal (command result)
   "Execute COMMAND non-interactively and compare it to RESULT."
-  (should (eshell-command-result--equal
-           command
-           (eshell-test-command-result command)
-           result)))
+  (ert-info (#'eshell-get-debug-logs :prefix "Command logs: ")
+    (should (eshell-command-result--equal
+             command
+             (eshell-test-command-result command)
+             result))))
 
 (provide 'eshell-tests-helpers)
 
-- 
2.25.1