faft: Separate the run_shell_command method that returns a result.

In CL:1863838, the behavior for block=True changed, breaking tests.
Before, it waited for process to exit; after, it waited for stdout
to be read as well.

This commit changes the method back to its old behavior, and adds a
separate method to give a result with stdout, stderr, and returncode.

BUG=b:147227865
TEST=Run firmware_FAFTRPC.system

Change-Id: Ie516a8baed2b5d1a19ca764e62f037e224da16e3
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1988732
Reviewed-by: Mary Ruthven <mruthven@chromium.org>
Tested-by: Dana Goyette <dgoyette@chromium.org>
Commit-Queue: Dana Goyette <dgoyette@chromium.org>
diff --git a/client/cros/faft/rpc_functions.py b/client/cros/faft/rpc_functions.py
index a14d5a4..8f75386 100644
--- a/client/cros/faft/rpc_functions.py
+++ b/client/cros/faft/rpc_functions.py
@@ -766,8 +766,8 @@
 
         @return: A string of the model name.
         """
-        model = cros_config.call_cros_config_get_output('/ name',
-                self._os_if.run_shell_command)
+        model = cros_config.call_cros_config_get_output(
+                '/ name', self._os_if.run_shell_command_get_result)
         if not model:
             raise Exception('Failed getting model name from cros_config')
         return model
diff --git a/client/cros/faft/utils/firmware_updater.py b/client/cros/faft/utils/firmware_updater.py
index 6a06efd..9c54649 100644
--- a/client/cros/faft/utils/firmware_updater.py
+++ b/client/cros/faft/utils/firmware_updater.py
@@ -405,7 +405,7 @@
         @type shellball: str | None
         """
         model_result = cros_config.call_cros_config_get_output(
-                '/ name', self.os_if.run_shell_command)
+                '/ name', self.os_if.run_shell_command_get_result)
 
         if not model_result:
             return
diff --git a/client/cros/faft/utils/os_interface.py b/client/cros/faft/utils/os_interface.py
index a891bbb..739ca0e 100644
--- a/client/cros/faft/utils/os_interface.py
+++ b/client/cros/faft/utils/os_interface.py
@@ -93,19 +93,27 @@
                                 This should be set for RPC commands that alter
                                 the OS or firmware in some persistent way.
 
-        @return: the result of the command, or None if skipped for test mode.
-        @rtype:  autotest_lib.client.common_lib.utils.CmdResult | None
         @raise autotest_lib.client.common_lib.error.CmdError: if command fails
         """
         if self.test_mode and modifies_device:
             self.log('[SKIPPED] %s' % cmd)
         else:
-            return self.shell.run_command(cmd)
+            self.shell.run_command(cmd)
 
     def run_shell_command_check_output(self, cmd, success_token):
         """Run shell command and check its stdout for a string."""
         return self.shell.run_command_check_output(cmd, success_token)
 
+    def run_shell_command_get_result(self, cmd, ignore_status=False):
+        """Run shell command and get a CmdResult object as a result.
+
+        @param cmd: the command to run
+        @param ignore_status: if True, do not raise CmdError, even if rc != 0.
+        @rtype: autotest_lib.client.common_lib.utils.CmdResult
+        @raise autotest_lib.client.common_lib.error.CmdError: if command fails
+        """
+        return self.shell.run_command_get_result(cmd, ignore_status)
+
     def run_shell_command_get_status(self, cmd):
         """Run shell command and return its return code."""
         return self.shell.run_command_get_status(cmd)
diff --git a/client/cros/faft/utils/shell_wrapper.py b/client/cros/faft/utils/shell_wrapper.py
index fe7000d..3aec171 100644
--- a/client/cros/faft/utils/shell_wrapper.py
+++ b/client/cros/faft/utils/shell_wrapper.py
@@ -48,23 +48,42 @@
         all output.
 
         @raise error.CmdError: if block is True and command fails (rc!=0)
-        @return: the result, if block is True
-
-        @rtype: utils.CmdResult | None
         """
         start_time = time.time()
-        process = self._run_command(cmd)
-        if block:
+        process = self._run_command(cmd, block)
+        if process.returncode:
             returncode = process.returncode
             stdout = process.stdout.read()
             stderr = process.stderr.read()
             duration = time.time() - start_time
             result = utils.CmdResult(cmd, stdout, stderr, returncode, duration)
-            if returncode:
-                self._os_if.log('Command failed.\n%s' % result)
-                raise error.CmdError(cmd, result)
-            else:
-                return result
+            self._os_if.log('Command failed.\n%s' % result)
+            raise error.CmdError(cmd, result)
+
+    def run_command_get_result(self, cmd, ignore_status=False):
+        """Run a shell command, and get the result (output and returncode).
+
+        @param ignore_status: if True, do not raise CmdError, even if rc != 0.
+        @raise error.CmdError: if command fails (rc!=0) and not ignore_result
+        @return the result of the command
+        @rtype: utils.CmdResult
+        """
+        start_time = time.time()
+
+        process = self._run_command(cmd, block=True)
+
+        returncode = process.returncode
+        stdout = process.stdout.read()
+        stderr = process.stderr.read()
+        duration = time.time() - start_time
+        result = utils.CmdResult(cmd, stdout, stderr, returncode, duration)
+
+        if returncode and not ignore_status:
+            self._os_if.log('Command failed:\n%s' % result)
+            raise error.CmdError(cmd, result)
+
+        self._os_if.log('Command result:\n%s' % result)
+        return result
 
     def run_command_check_output(self, cmd, success_token):
         """Run a command and check whether standard output contains some string.