autotest: servo: Produce more meaningful exception messages from servod.

The underlying exception in servod is masked currently by the
xmlrpclib.Fault exception.  This CL changes that relatively
meaningless message like:
 <Fault 1: "<type 'exceptions.AttributeError'>:'tca6416' object has no
 attribute 'hw_driver'">

To something more meaningful like:
  FAIL: Setting 'lid_open' to 'no' :: No control named lid_open

BUG=chrome-os-partner:10908
TEST=./run_remote_tests.sh --remote=<host> --servo platform_CloseOpenLid --board=<board>

Tested by modifying servod's xml files to remove lid_open control to
see failure.

Change-Id: I594dc13f8fff4c43e35f0e020da9473c11c7453c
Reviewed-on: https://gerrit.chromium.org/gerrit/27243
Tested-by: Todd Broch <tbroch@chromium.org>
Reviewed-by: Vic Yang <victoryang@chromium.org>
Commit-Ready: Todd Broch <tbroch@chromium.org>
diff --git a/server/cros/servo.py b/server/cros/servo.py
index 2a852ec..5969321 100644
--- a/server/cros/servo.py
+++ b/server/cros/servo.py
@@ -5,7 +5,7 @@
 # Expects to be run in an environment with sudo and no interactive password
 # prompt, such as within the Chromium OS development chroot.
 
-import logging, os, select, subprocess, sys, time, xmlrpclib
+import logging, os, re, select, subprocess, sys, time, xmlrpclib
 from autotest_lib.client.bin import utils as client_utils
 from autotest_lib.client.common_lib import error
 from autotest_lib.server import utils
@@ -304,10 +304,36 @@
         self.set('warm_reset', 'off')
 
 
+    def _get_xmlrpclib_exception(self, xmlexc):
+        """Get meaningful exception string from xmlrpc.
+
+        Args:
+            xmlexc: xmlrpclib.Fault object
+
+        xmlrpclib.Fault.faultString has the following format:
+
+        <type 'exception type'>:'actual error message'
+
+        Parse and return the real exception from the servod side instead of the
+        less meaningful one like,
+           <Fault 1: "<type 'exceptions.AttributeError'>:'tca6416' object has no
+           attribute 'hw_driver'">
+
+        Returns:
+            string of underlying exception raised in servod.
+        """
+        return re.sub('^.*>:', '', xmlexc.faultString)
+
+
     def get(self, gpio_name):
         """Get the value of a gpio from Servod."""
         assert gpio_name
-        return self._server.get(gpio_name)
+        try:
+            return self._server.get(gpio_name)
+        except  xmlrpclib.Fault as e:
+            err_msg = "Getting '%s' :: %s" % \
+                (gpio_name, self._get_xmlrpclib_exception(e))
+            raise error.TestFail(err_msg)
 
 
     def set(self, gpio_name, gpio_value):
@@ -328,7 +354,12 @@
         """Set the value of a gpio using Servod."""
         assert gpio_name and gpio_value
         logging.info('Setting %s to %s', gpio_name, gpio_value)
-        self._server.set(gpio_name, gpio_value)
+        try:
+            self._server.set(gpio_name, gpio_value)
+        except  xmlrpclib.Fault as e:
+            err_msg = "Setting '%s' to '%s' :: %s" % \
+                (gpio_name, gpio_value, self._get_xmlrpclib_exception(e))
+            raise error.TestFail(err_msg)
 
 
     # TODO(waihong) It may fail if multiple servo's are connected to the same