Force device unroot in the multi-device setup

In mobly, it will root the device if the device is rootable.
If there is some operation needs root permission,
the test result will be inconsistent between user and userdebug build.

Flag: EXEMPT mainline
Bug: 419423721
Test: atest CtsConnectivityMultiDevicesTestCases
Change-Id: Ib962b565aeaf8f0e1737b700a16420277e088e6b
This commit is contained in:
Jimi Chen 2025-05-28 13:13:17 +00:00
commit cfc8a85432
2 changed files with 87 additions and 0 deletions

View file

@ -12,8 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import re
import time
import types
from mobly import utils
from mobly.controllers import android_device
from mobly.controllers.android_device_lib import adb
from net_tests_utils.host.python import assert_utils
BYTE_DECODE_UTF_8 = 'utf-8'
@ -103,6 +108,73 @@ def get_dumpsys_for_service(
return adb_shell(ad, 'dumpsys ' + service)
def unroot_adb(self) -> bytes:
"""Executes the 'adb unroot' command on the device with retry logic.
This method is intended to be dynamically attached to an AdbProxy instance
(or similar object) that provides an '_exec_adb_cmd' method for executing
ADB commands. It attempts to unroot the device, and if an AdbError occurs,
it retries the command a specified number of times with an exponentially
increasing delay.
Args:
self: The instance of the ADB client (e.g., an AdbProxy object) that has the
'_exec_adb_cmd' method available to run ADB commands.
Returns:
bytes: The raw byte output from the 'adb unroot' command's successful
execution.
Raises:
adb.AdbError: If the 'adb unroot' command fails after all specified
retry attempts.
"""
retry_interval = adb.ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC
for attempt in range(adb.ADB_ROOT_RETRY_ATTEMPTS):
try:
# Assuming 'self' here is an object that has a '_exec_adb_cmd' method,
# similar to your original 'root' method's context (e.g., an AdbProxy).
return self._exec_adb_cmd(
'unroot', args=None, shell=False, timeout=None, stderr=None
)
except adb.AdbError as e:
if attempt + 1 < adb.ADB_ROOT_RETRY_ATTEMPTS:
# Decode stderr for logging, as Mobly often provides bytes
decoded_stderr = e.stderr.decode('utf-8', errors='ignore').strip()
logging.error(
f'Retry the command "{utils.cli_cmd_to_string(e.cmd)}" since Error'
f' "{decoded_stderr}" occurred (attempt'
f' {attempt + 1}/{adb.ADB_ROOT_RETRY_ATTEMPTS}).'
)
time.sleep(retry_interval)
retry_interval *= 2
else:
raise e
def unroot(ad: android_device.AndroidDevice):
"""Dynamically unroots an Android device by attaching 'unroot_adb' to its ADB proxy.
This function addresses a potential issue where Mobly might attempt to root
a device upon registration. When running user or userdebug builds,
this automatic rooting can lead to unexpected or inconsistent device behavior.
To mitigate this, this function temporarily attaches the 'unroot_adb' method
to the device's ADB proxy (`ad.adb`), executes it, and then removes it,
ensuring the device returns to an unrooted state and the ADB proxy's state
remains clean.
Args:
ad: The `android_device.AndroidDevice` instance to be unrooted.
"""
attach_method_name = 'unroot_adb'
try:
setattr(ad.adb, attach_method_name, types.MethodType(unroot_adb, ad.adb))
getattr(ad.adb, attach_method_name)()
ad.adb.wait_for_device()
finally:
delattr(ad.adb, attach_method_name)
def adb_shell(ad: android_device.AndroidDevice, shell_cmd: str) -> str:
"""Runs adb shell command.

View file

@ -24,9 +24,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from mobly import asserts
from mobly import base_test
from mobly import utils
from mobly.controllers import android_device
from net_tests_utils.host.python import adb_utils
CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = 'com.google.snippet.connectivity'
@ -39,6 +41,19 @@ class MultiDevicesTestBase(base_test.BaseTestClass):
android_device, min_number=2
)
if self.clientDevice.is_adb_root:
adb_utils.unroot(self.clientDevice)
if self.serverDevice.is_adb_root:
adb_utils.unroot(self.serverDevice)
asserts.assert_false(
self.clientDevice.is_adb_root, 'client device should be unroot'
)
asserts.assert_false(
self.serverDevice.is_adb_root, 'server device should be unroot'
)
def setup_device(device):
device.load_snippet(
'connectivity_multi_devices_snippet',