faft: Allow running shell commands non-blocking.

In order to handle commands that tests shouldn't wait for, it's
helpful to have a way to do it via the python code, instead of via
the shell, which might not exit until stdin/stdout are closed.

BUG=b:147227865
TEST=Run firmware_FAFTRPC.system
Change-Id: Idf6f14985372aa58b82cc9152b204c1cd0fbe1a1
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1880732
Tested-by: Dana Goyette <dgoyette@chromium.org>
Commit-Queue: Dana Goyette <dgoyette@chromium.org>
Reviewed-by: Mary Ruthven <mruthven@chromium.org>
diff --git a/client/cros/faft/rpc_functions.py b/client/cros/faft/rpc_functions.py
index 8f75386..07cba35 100644
--- a/client/cros/faft/rpc_functions.py
+++ b/client/cros/faft/rpc_functions.py
@@ -713,12 +713,13 @@
             os.remove(self._os_if.log_file)
         return log
 
-    def run_shell_command(self, command):
+    def run_shell_command(self, command, block=True):
         """Run shell command.
 
         @param command: A shell command to be run.
+        @param block: if True (default), wait for command to finish
         """
-        self._os_if.run_shell_command(command)
+        self._os_if.run_shell_command(command, block=block)
 
     def run_shell_command_check_output(self, command, success_token):
         """Run shell command and check its stdout for a string.
diff --git a/client/cros/faft/utils/os_interface.py b/client/cros/faft/utils/os_interface.py
index 739ca0e..49f2aad 100644
--- a/client/cros/faft/utils/os_interface.py
+++ b/client/cros/faft/utils/os_interface.py
@@ -84,10 +84,11 @@
 
         self.cs = Crossystem(self)
 
-    def run_shell_command(self, cmd, modifies_device=False):
+    def run_shell_command(self, cmd, block=True, modifies_device=False):
         """Run a shell command.
 
         @param cmd: the command to run
+        @param block: if True (default), wait for command to finish
         @param modifies_device: If True and running in test mode, just log
                                 the command, but don't actually run it.
                                 This should be set for RPC commands that alter
@@ -98,7 +99,7 @@
         if self.test_mode and modifies_device:
             self.log('[SKIPPED] %s' % cmd)
         else:
-            self.shell.run_command(cmd)
+            self.shell.run_command(cmd, block=block)
 
     def run_shell_command_check_output(self, cmd, success_token):
         """Run shell command and check its stdout for a string."""
diff --git a/client/cros/faft/utils/shell_wrapper.py b/client/cros/faft/utils/shell_wrapper.py
index 3aec171..b71585a 100644
--- a/client/cros/faft/utils/shell_wrapper.py
+++ b/client/cros/faft/utils/shell_wrapper.py
@@ -47,11 +47,13 @@
         outputs on the console and dump them into the log. Otherwise suppress
         all output.
 
+        @param block: if True (default), wait for command to finish
         @raise error.CmdError: if block is True and command fails (rc!=0)
         """
         start_time = time.time()
         process = self._run_command(cmd, block)
-        if process.returncode:
+        if block and process.returncode:
+            # Grab output only if an error occurred
             returncode = process.returncode
             stdout = process.stdout.read()
             stderr = process.stderr.read()
diff --git a/server/site_tests/firmware_FAFTRPC/config.py b/server/site_tests/firmware_FAFTRPC/config.py
index ba902ff..33ef273 100644
--- a/server/site_tests/firmware_FAFTRPC/config.py
+++ b/server/site_tests/firmware_FAFTRPC/config.py
@@ -102,10 +102,19 @@
                 "silence_result": True,
             },
             {
-                "method_names": [
-                    "run_shell_command",
-                    "run_shell_command_get_status",
+                "method_name": "run_shell_command",
+                "passing_args": [
+                    ("ls -l", ),
+                    ("ls -l", False),
+                    ("ls -l", True)
                 ],
+                "failing_args": [
+                    NO_ARGS,
+                    ("ls", "-l", 'foo'),
+                ],
+            },
+            {
+                "method_name": "run_shell_command_get_status",
                 "passing_args": [
                     ("ls", ),
                 ],