Allow overriding props with setters via config in `AndroidDevice`. (#644)
diff --git a/mobly/controllers/android_device.py b/mobly/controllers/android_device.py
index 0ac605a..f9122ef 100644
--- a/mobly/controllers/android_device.py
+++ b/mobly/controllers/android_device.py
@@ -336,6 +336,7 @@
Raises:
Error: No devices are matched.
"""
+
def _get_device_filter(ad):
for k, v in kwargs.items():
if not hasattr(ad, k):
@@ -437,6 +438,7 @@
services: ServiceManager, the manager of long-running services on the
device.
"""
+
def __init__(self, serial=''):
self._serial = str(serial)
# logging.log_path only exists when this is used in an Mobly test run.
@@ -839,7 +841,7 @@
Error: The config is trying to overwrite an existing attribute.
"""
for k, v in config.items():
- if hasattr(self, k):
+ if hasattr(self, k) and k not in _ANDROID_DEVICE_SETTABLE_PROPS:
raise DeviceError(
self,
('Attribute %s already exists with value %s, cannot set '
@@ -1089,6 +1091,11 @@
return self.__getattribute__(name)
+# Properties in AndroidDevice that have setters.
+# This line has to live below the AndroidDevice code.
+_ANDROID_DEVICE_SETTABLE_PROPS = utils.get_settable_properties(AndroidDevice)
+
+
class AndroidDeviceLoggerAdapter(logging.LoggerAdapter):
"""A wrapper class that adds a prefix to each log line.
@@ -1103,6 +1110,7 @@
Then each log line added by my_log will have a prefix
'[AndroidDevice|<tag>]'
"""
+
def process(self, msg, kwargs):
msg = _DEBUG_PREFIX_TEMPLATE % (self.extra['tag'], msg)
return (msg, kwargs)
diff --git a/mobly/utils.py b/mobly/utils.py
index 2a60bae..68ce39f 100644
--- a/mobly/utils.py
+++ b/mobly/utils.py
@@ -549,6 +549,21 @@
return ' '.join([pipes.quote(arg) for arg in args])
+def get_settable_properties(cls):
+ """Gets the settable properties of a class.
+
+ Only returns the explicitly defined properties with setters.
+
+ Args:
+ cls: A class in Python.
+ """
+ results = []
+ for attr, value in vars(cls).items():
+ if isinstance(value, property) and value.fset is not None:
+ results.append(attr)
+ return results
+
+
def find_subclasses_in_module(base_classes, module):
"""Finds the subclasses of the given classes in the given module.
diff --git a/tests/mobly/controllers/android_device_test.py b/tests/mobly/controllers/android_device_test.py
index 490df50..6308833 100755
--- a/tests/mobly/controllers/android_device_test.py
+++ b/tests/mobly/controllers/android_device_test.py
@@ -45,6 +45,7 @@
"""This test class has unit tests for the implementation of everything
under mobly.controllers.android_device.
"""
+
def setUp(self):
# Set log_path to logging since mobly logger setup is not called.
if not hasattr(logging, 'log_path'):
@@ -276,6 +277,39 @@
self.assertIsNotNone(ad.services.snippets)
@mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
+ return_value=mock_android_device.MockAdbProxy(1))
+ @mock.patch('mobly.controllers.android_device_lib.fastboot.FastbootProxy',
+ return_value=mock_android_device.MockFastbootProxy(1))
+ @mock.patch('mobly.utils.create_dir')
+ def test_AndroidDevice_load_config(self, create_dir_mock, FastbootProxy,
+ MockAdbProxy):
+ mock_serial = '1'
+ config = {
+ 'space': 'the final frontier',
+ 'number': 1,
+ 'debug_tag': 'my_tag'
+ }
+ ad = android_device.AndroidDevice(serial=mock_serial)
+ ad.load_config(config)
+ self.assertEqual(ad.space, 'the final frontier')
+ self.assertEqual(ad.number, 1)
+ self.assertEqual(ad.debug_tag, 'my_tag')
+
+ @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
+ return_value=mock_android_device.MockAdbProxy(1))
+ @mock.patch('mobly.controllers.android_device_lib.fastboot.FastbootProxy',
+ return_value=mock_android_device.MockFastbootProxy(1))
+ @mock.patch('mobly.utils.create_dir')
+ def test_AndroidDevice_load_config_dup(self, create_dir_mock,
+ FastbootProxy, MockAdbProxy):
+ mock_serial = '1'
+ config = {'serial': 'new_serial'}
+ ad = android_device.AndroidDevice(serial=mock_serial)
+ with self.assertRaisesRegex(android_device.DeviceError,
+ 'Attribute serial already exists with'):
+ ad.load_config(config)
+
+ @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
return_value=mock_android_device.MockAdbProxy('1'))
@mock.patch('mobly.controllers.android_device_lib.fastboot.FastbootProxy',
return_value=mock_android_device.MockFastbootProxy('1'))
diff --git a/tests/mobly/utils_test.py b/tests/mobly/utils_test.py
index 714c238..207273c 100755
--- a/tests/mobly/utils_test.py
+++ b/tests/mobly/utils_test.py
@@ -43,6 +43,7 @@
"""This test class has unit tests for the implementation of everything
under mobly.utils.
"""
+
def setUp(self):
system = platform.system()
self.tmp_dir = tempfile.mkdtemp()
@@ -247,6 +248,30 @@
cmd = 'adb -s meme do something ab_cd'
self.assertEqual(utils.cli_cmd_to_string(cmd), cmd)
+ def test_get_settable_properties(self):
+ class SomeClass(object):
+ regular_attr = 'regular_attr'
+ _foo = 'foo'
+ _bar = 'bar'
+
+ @property
+ def settable_prop(self):
+ return self._foo
+
+ @settable_prop.setter
+ def settable_prop(self, new_foo):
+ self._foo = new_foo
+
+ @property
+ def readonly_prop(self):
+ return self._bar
+
+ def func(self):
+ """Func should not be considered as a settable prop."""
+
+ actual = utils.get_settable_properties(SomeClass)
+ self.assertEqual(actual, ['settable_prop'])
+
def test_find_subclasses_in_module_when_one_subclass(self):
subclasses = utils.find_subclasses_in_module([base_test.BaseTestClass],
integration_test)