Repository: google/mobly

Issue:
Log the contents of config file at the debug level early
This helps in debugging remote user's malformed json/yaml or configs that don't adhere to schema.

Discussion:

Patch:
diff --git a/mobly/config_parser.py b/mobly/config_parser.py
index aa43c03..b873af0 100644
--- a/mobly/config_parser.py
+++ b/mobly/config_parser.py
@@ -16,6 +16,7 @@ from builtins import str
 
 import copy
 import io
+import pprint
 import os
 import yaml
 
@@ -189,4 +190,7 @@ class TestRunConfig(object):
         return copy.deepcopy(self)
 
     def __str__(self):
-        return str(self.__dict__)
+        content = dict(self.__dict__)
+        content.pop('summary_writer')
+        content.pop('register_controller')
+        return pprint.pformat(content)
diff --git a/mobly/controllers/android_device.py b/mobly/controllers/android_device.py
index 504f671..4d89af8 100644
--- a/mobly/controllers/android_device.py
+++ b/mobly/controllers/android_device.py
@@ -436,8 +436,9 @@ class AndroidDevice(object):
         self._log_path = os.path.join(self._log_path_base,
                                       'AndroidDevice%s' % self._serial)
         self._debug_tag = self._serial
-        self.log = AndroidDeviceLoggerAdapter(logging.getLogger(),
-                                              {'tag': self.debug_tag})
+        self.log = AndroidDeviceLoggerAdapter(logging.getLogger(), {
+            'tag': self.debug_tag
+        })
         self.sl4a = None
         self.ed = None
         self._adb_logcat_process = None
@@ -783,15 +784,7 @@ class AndroidDevice(object):
 
     @property
     def is_rootable(self):
-        """If the build type is 'user', the device is not rootable.
-
-        Other possible build types are 'userdebug' and 'eng', both are rootable.
-        We are checking the last four chars of the clean stdout because the
-        stdout of the adb command could be polluted with other info like adb
-        server startup message.
-        """
-        build_type_output = self.adb.getprop('ro.build.type').lower()
-        return build_type_output[-4:] != 'user'
+        return self.adb.getprop('ro.debuggable') == '1'
 
     @property
     def model(self):
diff --git a/mobly/controllers/android_device_lib/jsonrpc_client_base.py b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
index 279360d..498434d 100644
--- a/mobly/controllers/android_device_lib/jsonrpc_client_base.py
+++ b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
@@ -19,7 +19,7 @@ The JSON protocol expected by this module is:
 
     Request:
     {
-        "id": <monotonically increasing integer containing the ID of 
+        "id": <monotonically increasing integer containing the ID of
                this request>
         "method": <string containing the name of the method to execute>
         "params": <JSON array containing the arguments to the method>
@@ -239,6 +239,7 @@ class JsonRpcClientBase(object):
         try:
             self._client.write(msg.encode("utf8") + b'\n')
             self._client.flush()
+            self.log.debug('Snippet sent %s.', msg)
         except socket.error as e:
             raise Error(
                 self._ad,
@@ -255,7 +256,9 @@ class JsonRpcClientBase(object):
             Error: a socket error occurred during the read.
         """
         try:
-            return self._client.readline()
+            response = self._client.readline()
+            self.log.debug('Snippet received: %s', response)
+            return response
         except socket.error as e:
             raise Error(
                 self._ad,
@@ -299,7 +302,7 @@ class JsonRpcClientBase(object):
         if not response:
             raise ProtocolError(self._ad,
                                 ProtocolError.NO_RESPONSE_FROM_SERVER)
-        result = json.loads(str(response, encoding="utf8"))
+        result = json.loads(str(response, encoding='utf8'))
         if result['error']:
             raise ApiError(self._ad, result['error'])
         if result['id'] != apiid:


Test patch:
diff --git a/mobly/test_runner.py b/mobly/test_runner.py
index af834e8..e4fa9d8 100644
--- a/mobly/test_runner.py
+++ b/mobly/test_runner.py
@@ -381,8 +381,9 @@ class TestRunner(object):
             test_class: class, test class to execute.
             tests: Optional list of test names within the class to execute.
         """
-
         with test_class(config) as test_instance:
+            logging.debug('Executing test class "%s" with config: %s',
+                          test_class.__name__, config)
             try:
                 cls_result = test_instance.run(tests)
                 self.results += cls_result
diff --git a/tests/mobly/config_parser_test.py b/tests/mobly/config_parser_test.py
index ea16668..44dfbdc 100644
--- a/tests/mobly/config_parser_test.py
+++ b/tests/mobly/config_parser_test.py
@@ -56,6 +56,11 @@ class OutputTest(unittest.TestCase):
         config = config_parser._load_config_file(tmp_file_path)
         self.assertEqual(config['TestBeds'][0]['Name'], u'\u901a')
 
+    def test_run_config_type(self):
+        config = config_parser.TestRunConfig()
+        self.assertNotIn('summary_writer', str(config))
+        self.assertNotIn('register_controller', str(config))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/mobly/controllers/android_device_test.py b/tests/mobly/controllers/android_device_test.py
index e12e511..eb5129a 100755
--- a/tests/mobly/controllers/android_device_test.py
+++ b/tests/mobly/controllers/android_device_test.py
@@ -706,7 +706,8 @@ class AndroidDeviceTest(unittest.TestCase):
             self, MockFastboot, MockAdbProxy):
         mock_serial = '1'
         mock_adb_proxy = MockAdbProxy.return_value
-        mock_adb_proxy.getprop.return_value = 'userdebug'
+        # Set getprop to return '1' to indicate the device is rootable.
+        mock_adb_proxy.getprop.return_value = '1'
         mock_adb_proxy.has_shell_command.side_effect = lambda command: {
             'logpersist.start': True,
             'logpersist.stop': True, }[command]