|
@@ -5,2443 +5,6 @@
|
|
# Parent 7bc9cdc13e72d98c1700debfea3e37aee4ab2354
|
|
# Parent 7bc9cdc13e72d98c1700debfea3e37aee4ab2354
|
|
Bug 1440714 - Remove DeviceManagerADB and Droid classes from mozdevice; r=bc
|
|
Bug 1440714 - Remove DeviceManagerADB and Droid classes from mozdevice; r=bc
|
|
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/adb_tests/test_device_running_adb_as_root.py b/testing/mozbase/mozdevice/adb_tests/test_device_running_adb_as_root.py
|
|
|
|
-deleted file mode 100644
|
|
|
|
---- a/testing/mozbase/mozdevice/adb_tests/test_device_running_adb_as_root.py
|
|
|
|
-+++ /dev/null
|
|
|
|
-@@ -1,52 +0,0 @@
|
|
|
|
--"""
|
|
|
|
-- This test is to test devices that adbd does not get started as root.
|
|
|
|
-- Specifically devices that have ro.secure == 1 and ro.debuggable == 1
|
|
|
|
--
|
|
|
|
-- Running this test case requires various reboots which makes it a
|
|
|
|
-- very slow test case to run.
|
|
|
|
--"""
|
|
|
|
--
|
|
|
|
--from __future__ import absolute_import, print_function
|
|
|
|
--
|
|
|
|
--import unittest
|
|
|
|
--import sys
|
|
|
|
--
|
|
|
|
--from mozdevice import DeviceManagerADB
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class TestFileOperations(unittest.TestCase):
|
|
|
|
--
|
|
|
|
-- def setUp(self):
|
|
|
|
-- dm = DeviceManagerADB()
|
|
|
|
-- dm.reboot(wait=True)
|
|
|
|
--
|
|
|
|
-- def test_run_adb_as_root_parameter(self):
|
|
|
|
-- dm = DeviceManagerADB()
|
|
|
|
-- self.assertTrue(dm.processInfo("adbd")[2] != "root")
|
|
|
|
-- dm = DeviceManagerADB(runAdbAsRoot=True)
|
|
|
|
-- self.assertTrue(dm.processInfo("adbd")[2] == "root")
|
|
|
|
--
|
|
|
|
-- def test_after_reboot_adb_runs_as_root(self):
|
|
|
|
-- dm = DeviceManagerADB(runAdbAsRoot=True)
|
|
|
|
-- self.assertTrue(dm.processInfo("adbd")[2] == "root")
|
|
|
|
-- dm.reboot(wait=True)
|
|
|
|
-- self.assertTrue(dm.processInfo("adbd")[2] == "root")
|
|
|
|
--
|
|
|
|
-- def tearDown(self):
|
|
|
|
-- dm = DeviceManagerADB()
|
|
|
|
-- dm.reboot()
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--if __name__ == "__main__":
|
|
|
|
-- dm = DeviceManagerADB()
|
|
|
|
-- if not dm.devices():
|
|
|
|
-- print("There are no connected adb devices")
|
|
|
|
-- sys.exit(1)
|
|
|
|
-- else:
|
|
|
|
-- if not (int(dm._runCmd(["shell", "getprop", "ro.secure"]).output[0]) and
|
|
|
|
-- int(dm._runCmd(["shell", "getprop", "ro.debuggable"]).output[0])):
|
|
|
|
-- print("This test case is meant for devices with devices that start "
|
|
|
|
-- "adbd as non-root and allows for adbd to be restarted as root.")
|
|
|
|
-- sys.exit(1)
|
|
|
|
--
|
|
|
|
-- unittest.main()
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/adb_tests/test_devicemanagerADB.py b/testing/mozbase/mozdevice/adb_tests/test_devicemanagerADB.py
|
|
|
|
-deleted file mode 100644
|
|
|
|
---- a/testing/mozbase/mozdevice/adb_tests/test_devicemanagerADB.py
|
|
|
|
-+++ /dev/null
|
|
|
|
-@@ -1,220 +0,0 @@
|
|
|
|
--"""
|
|
|
|
-- Info:
|
|
|
|
-- This tests DeviceManagerADB with a real device
|
|
|
|
--
|
|
|
|
-- Requirements:
|
|
|
|
-- - You must have a device connected
|
|
|
|
-- - It should be listed under 'adb devices'
|
|
|
|
--
|
|
|
|
-- Notes:
|
|
|
|
-- - Not all functions have been covered.
|
|
|
|
-- In particular, functions from the parent class
|
|
|
|
-- - No testing of properties is done
|
|
|
|
-- - The test case are very simple and it could be
|
|
|
|
-- done with deeper inspection of the return values
|
|
|
|
--
|
|
|
|
-- Author(s):
|
|
|
|
-- - Armen Zambrano <armenzg@mozilla.com>
|
|
|
|
--
|
|
|
|
-- Functions that are not being tested:
|
|
|
|
-- - launchProcess - DEPRECATED
|
|
|
|
-- - getIP
|
|
|
|
-- - recordLogcat
|
|
|
|
-- - saveScreenshot
|
|
|
|
-- - validateDir
|
|
|
|
-- - mkDirs
|
|
|
|
-- - getDeviceRoot
|
|
|
|
-- - shellCheckOutput
|
|
|
|
-- - processExist
|
|
|
|
--
|
|
|
|
-- I assume these functions are only useful for Android
|
|
|
|
-- - getAppRoot()
|
|
|
|
-- - updateApp()
|
|
|
|
-- - uninstallApp()
|
|
|
|
-- - uninstallAppAndReboot()
|
|
|
|
--"""
|
|
|
|
--from __future__ import absolute_import, print_function
|
|
|
|
--
|
|
|
|
--import os
|
|
|
|
--import re
|
|
|
|
--import socket
|
|
|
|
--import sys
|
|
|
|
--import tempfile
|
|
|
|
--import unittest
|
|
|
|
--from StringIO import StringIO
|
|
|
|
--
|
|
|
|
--from mozdevice import DeviceManagerADB, DMError
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--def find_mount_permissions(dm, mount_path):
|
|
|
|
-- for mount_point in dm._runCmd(["shell", "mount"]).output:
|
|
|
|
-- if mount_point.find(mount_path) > 0:
|
|
|
|
-- return re.search('(ro|rw)(?=,)', mount_point).group(0)
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class DeviceManagerADBTestCase(unittest.TestCase):
|
|
|
|
-- tempLocalDir = "tempDir"
|
|
|
|
-- tempLocalFile = os.path.join(tempLocalDir, "tempfile.txt")
|
|
|
|
-- tempRemoteDir = None
|
|
|
|
-- tempRemoteFile = None
|
|
|
|
-- tempRemoteSystemFile = None
|
|
|
|
--
|
|
|
|
-- def setUp(self):
|
|
|
|
-- self.assertTrue(find_mount_permissions(self.dm, "/system"), "ro")
|
|
|
|
--
|
|
|
|
-- self.assertTrue(os.path.exists(self.tempLocalDir))
|
|
|
|
-- self.assertTrue(os.path.exists(self.tempLocalFile))
|
|
|
|
--
|
|
|
|
-- if self.dm.fileExists(self.tempRemoteFile):
|
|
|
|
-- self.dm.removeFile(self.tempRemoteFile)
|
|
|
|
-- self.assertFalse(self.dm.fileExists(self.tempRemoteFile))
|
|
|
|
--
|
|
|
|
-- if self.dm.fileExists(self.tempRemoteSystemFile):
|
|
|
|
-- self.dm.removeFile(self.tempRemoteSystemFile)
|
|
|
|
--
|
|
|
|
-- self.assertTrue(self.dm.dirExists(self.tempRemoteDir))
|
|
|
|
--
|
|
|
|
-- @classmethod
|
|
|
|
-- def setUpClass(self):
|
|
|
|
-- self.dm = DeviceManagerADB()
|
|
|
|
-- if not os.path.exists(self.tempLocalDir):
|
|
|
|
-- os.mkdir(self.tempLocalDir)
|
|
|
|
-- if not os.path.exists(self.tempLocalFile):
|
|
|
|
-- # Create empty file
|
|
|
|
-- open(self.tempLocalFile, 'w').close()
|
|
|
|
-- self.tempRemoteDir = self.dm.getTempDir()
|
|
|
|
-- self.tempRemoteFile = os.path.join(self.tempRemoteDir,
|
|
|
|
-- os.path.basename(self.tempLocalFile))
|
|
|
|
-- self.tempRemoteSystemFile = \
|
|
|
|
-- os.path.join("/system", os.path.basename(self.tempLocalFile))
|
|
|
|
--
|
|
|
|
-- @classmethod
|
|
|
|
-- def tearDownClass(self):
|
|
|
|
-- os.remove(self.tempLocalFile)
|
|
|
|
-- os.rmdir(self.tempLocalDir)
|
|
|
|
-- if self.dm.dirExists(self.tempRemoteDir):
|
|
|
|
-- # self.tempRemoteFile will get deleted with it
|
|
|
|
-- self.dm.removeDir(self.tempRemoteDir)
|
|
|
|
-- if self.dm.fileExists(self.tempRemoteSystemFile):
|
|
|
|
-- self.dm.removeFile(self.tempRemoteSystemFile)
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class TestFileOperations(DeviceManagerADBTestCase):
|
|
|
|
--
|
|
|
|
-- def test_make_and_remove_directory(self):
|
|
|
|
-- dir1 = os.path.join(self.tempRemoteDir, "dir1")
|
|
|
|
-- self.assertFalse(self.dm.dirExists(dir1))
|
|
|
|
-- self.dm.mkDir(dir1)
|
|
|
|
-- self.assertTrue(self.dm.dirExists(dir1))
|
|
|
|
-- self.dm.removeDir(dir1)
|
|
|
|
-- self.assertFalse(self.dm.dirExists(dir1))
|
|
|
|
--
|
|
|
|
-- def test_push_and_remove_file(self):
|
|
|
|
-- self.dm.pushFile(self.tempLocalFile, self.tempRemoteFile)
|
|
|
|
-- self.assertTrue(self.dm.fileExists(self.tempRemoteFile))
|
|
|
|
-- self.dm.removeFile(self.tempRemoteFile)
|
|
|
|
-- self.assertFalse(self.dm.fileExists(self.tempRemoteFile))
|
|
|
|
--
|
|
|
|
-- def test_push_and_pull_file(self):
|
|
|
|
-- self.dm.pushFile(self.tempLocalFile, self.tempRemoteFile)
|
|
|
|
-- self.assertTrue(self.dm.fileExists(self.tempRemoteFile))
|
|
|
|
-- self.assertFalse(os.path.exists("pulled.txt"))
|
|
|
|
-- self.dm.getFile(self.tempRemoteFile, "pulled.txt")
|
|
|
|
-- self.assertTrue(os.path.exists("pulled.txt"))
|
|
|
|
-- os.remove("pulled.txt")
|
|
|
|
--
|
|
|
|
-- def test_push_and_pull_directory_and_list_files(self):
|
|
|
|
-- self.dm.removeDir(self.tempRemoteDir)
|
|
|
|
-- self.assertFalse(self.dm.dirExists(self.tempRemoteDir))
|
|
|
|
-- self.dm.pushDir(self.tempLocalDir, self.tempRemoteDir)
|
|
|
|
-- self.assertTrue(self.dm.dirExists(self.tempRemoteDir))
|
|
|
|
-- response = self.dm.listFiles(self.tempRemoteDir)
|
|
|
|
-- # The local dir that was pushed contains the tempLocalFile
|
|
|
|
-- self.assertIn(os.path.basename(self.tempLocalFile), response)
|
|
|
|
-- # Create a temp dir to pull to
|
|
|
|
-- temp_dir = tempfile.mkdtemp()
|
|
|
|
-- self.assertTrue(os.path.exists(temp_dir))
|
|
|
|
-- self.dm.getDirectory(self.tempRemoteDir, temp_dir)
|
|
|
|
-- self.assertTrue(os.path.exists(self.tempLocalFile))
|
|
|
|
--
|
|
|
|
-- def test_move_and_remove_directories(self):
|
|
|
|
-- dir1 = os.path.join(self.tempRemoteDir, "dir1")
|
|
|
|
-- dir2 = os.path.join(self.tempRemoteDir, "dir2")
|
|
|
|
--
|
|
|
|
-- self.assertFalse(self.dm.dirExists(dir1))
|
|
|
|
-- self.dm.mkDir(dir1)
|
|
|
|
-- self.assertTrue(self.dm.dirExists(dir1))
|
|
|
|
--
|
|
|
|
-- self.assertFalse(self.dm.dirExists(dir2))
|
|
|
|
-- self.dm.moveTree(dir1, dir2)
|
|
|
|
-- self.assertTrue(self.dm.dirExists(dir2))
|
|
|
|
--
|
|
|
|
-- self.dm.removeDir(dir1)
|
|
|
|
-- self.dm.removeDir(dir2)
|
|
|
|
-- self.assertFalse(self.dm.dirExists(dir1))
|
|
|
|
-- self.assertFalse(self.dm.dirExists(dir2))
|
|
|
|
--
|
|
|
|
-- def test_push_and_remove_system_file(self):
|
|
|
|
-- out = StringIO()
|
|
|
|
-- self.assertTrue(find_mount_permissions(self.dm, "/system") == "ro")
|
|
|
|
-- self.assertFalse(self.dm.fileExists(self.tempRemoteSystemFile))
|
|
|
|
-- self.assertRaises(DMError, self.dm.pushFile, self.tempLocalFile, self.tempRemoteSystemFile)
|
|
|
|
-- self.dm.shell(['mount', '-w', '-o', 'remount', '/system'], out)
|
|
|
|
-- self.assertTrue(find_mount_permissions(self.dm, "/system") == "rw")
|
|
|
|
-- self.assertFalse(self.dm.fileExists(self.tempRemoteSystemFile))
|
|
|
|
-- self.dm.pushFile(self.tempLocalFile, self.tempRemoteSystemFile)
|
|
|
|
-- self.assertTrue(self.dm.fileExists(self.tempRemoteSystemFile))
|
|
|
|
-- self.dm.removeFile(self.tempRemoteSystemFile)
|
|
|
|
-- self.assertFalse(self.dm.fileExists(self.tempRemoteSystemFile))
|
|
|
|
-- self.dm.shell(['mount', '-r', '-o', 'remount', '/system'], out)
|
|
|
|
-- out.close()
|
|
|
|
-- self.assertTrue(find_mount_permissions(self.dm, "/system") == "ro")
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class TestOther(DeviceManagerADBTestCase):
|
|
|
|
--
|
|
|
|
-- def test_get_list_of_processes(self):
|
|
|
|
-- self.assertEquals(type(self.dm.getProcessList()), list)
|
|
|
|
--
|
|
|
|
-- def test_get_current_time(self):
|
|
|
|
-- self.assertEquals(type(self.dm.getCurrentTime()), int)
|
|
|
|
--
|
|
|
|
-- def test_get_info(self):
|
|
|
|
-- self.assertEquals(type(self.dm.getInfo()), dict)
|
|
|
|
--
|
|
|
|
-- def test_list_devices(self):
|
|
|
|
-- self.assertEquals(len(list(self.dm.devices())), 1)
|
|
|
|
--
|
|
|
|
-- def test_shell(self):
|
|
|
|
-- out = StringIO()
|
|
|
|
-- self.dm.shell(["echo", "$COMPANY", ";", "pwd"], out,
|
|
|
|
-- env={"COMPANY": "Mozilla"}, cwd="/", timeout=4, root=True)
|
|
|
|
-- output = str(out.getvalue()).rstrip().splitlines()
|
|
|
|
-- out.close()
|
|
|
|
-- self.assertEquals(output, ['Mozilla', '/'])
|
|
|
|
--
|
|
|
|
-- def test_port_forwarding(self):
|
|
|
|
-- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
-- s.bind(("", 0))
|
|
|
|
-- port = s.getsockname()[1]
|
|
|
|
-- s.close()
|
|
|
|
-- # If successful then no exception is raised
|
|
|
|
-- self.dm.forward("tcp:%s" % port, "tcp:2828")
|
|
|
|
--
|
|
|
|
-- def test_port_forwarding_error(self):
|
|
|
|
-- self.assertRaises(DMError, self.dm.forward, "", "")
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--if __name__ == '__main__':
|
|
|
|
-- dm = DeviceManagerADB()
|
|
|
|
-- if not dm.devices():
|
|
|
|
-- print("There are no connected adb devices")
|
|
|
|
-- sys.exit(1)
|
|
|
|
--
|
|
|
|
-- if find_mount_permissions(dm, "/system") == "rw":
|
|
|
|
-- print("We've found out that /system is mounted as 'rw'. This is because the command "
|
|
|
|
-- "'adb remount' has been run before running this test case. Please reboot the "
|
|
|
|
-- "device and try again.")
|
|
|
|
-- sys.exit(1)
|
|
|
|
--
|
|
|
|
-- unittest.main()
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/mozdevice/__init__.py b/testing/mozbase/mozdevice/mozdevice/__init__.py
|
|
|
|
---- a/testing/mozbase/mozdevice/mozdevice/__init__.py
|
|
|
|
-+++ b/testing/mozbase/mozdevice/mozdevice/__init__.py
|
|
|
|
-@@ -3,15 +3,11 @@
|
|
|
|
- # You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
-
|
|
|
|
- from __future__ import absolute_import
|
|
|
|
-
|
|
|
|
- from .adb import ADBError, ADBProcessError, ADBRootError, ADBTimeoutError
|
|
|
|
- from .adb import ADBProcess, ADBCommand, ADBHost, ADBDevice
|
|
|
|
- from .adb_android import ADBAndroid
|
|
|
|
- from .adb_b2g import ADBB2G
|
|
|
|
--from .devicemanager import DeviceManager, DMError
|
|
|
|
--from .devicemanagerADB import DeviceManagerADB
|
|
|
|
--from .droid import DroidADB
|
|
|
|
-
|
|
|
|
- __all__ = ['ADBError', 'ADBProcessError', 'ADBRootError', 'ADBTimeoutError',
|
|
|
|
-- 'ADBProcess', 'ADBCommand', 'ADBHost', 'ADBDevice', 'ADBAndroid', 'ADBB2G',
|
|
|
|
-- 'DeviceManager', 'DMError', 'DeviceManagerADB', 'DroidADB']
|
|
|
|
-+ 'ADBProcess', 'ADBCommand', 'ADBHost', 'ADBDevice', 'ADBAndroid', 'ADBB2G']
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanager.py b/testing/mozbase/mozdevice/mozdevice/devicemanager.py
|
|
|
|
-deleted file mode 100644
|
|
|
|
---- a/testing/mozbase/mozdevice/mozdevice/devicemanager.py
|
|
|
|
-+++ /dev/null
|
|
|
|
-@@ -1,638 +0,0 @@
|
|
|
|
--# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
--# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
--# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
--
|
|
|
|
--from __future__ import absolute_import
|
|
|
|
--
|
|
|
|
--import hashlib
|
|
|
|
--import mozlog
|
|
|
|
--import logging
|
|
|
|
--import os
|
|
|
|
--import posixpath
|
|
|
|
--import re
|
|
|
|
--import struct
|
|
|
|
--import StringIO
|
|
|
|
--import zlib
|
|
|
|
--
|
|
|
|
--from functools import wraps
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class DMError(Exception):
|
|
|
|
-- "generic devicemanager exception."
|
|
|
|
--
|
|
|
|
-- def __init__(self, msg='', fatal=False):
|
|
|
|
-- self.msg = msg
|
|
|
|
-- self.fatal = fatal
|
|
|
|
--
|
|
|
|
-- def __str__(self):
|
|
|
|
-- return self.msg
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--def abstractmethod(method):
|
|
|
|
-- line = method.func_code.co_firstlineno
|
|
|
|
-- filename = method.func_code.co_filename
|
|
|
|
--
|
|
|
|
-- @wraps(method)
|
|
|
|
-- def not_implemented(*args, **kwargs):
|
|
|
|
-- raise NotImplementedError('Abstract method %s at File "%s", line %s '
|
|
|
|
-- 'should be implemented by a concrete class' %
|
|
|
|
-- (repr(method), filename, line))
|
|
|
|
-- return not_implemented
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class DeviceManager(object):
|
|
|
|
-- """
|
|
|
|
-- Represents a connection to a device. Once an implementation of this class
|
|
|
|
-- is successfully instantiated, you may do things like list/copy files to
|
|
|
|
-- the device, launch processes on the device, and install or remove
|
|
|
|
-- applications from the device.
|
|
|
|
--
|
|
|
|
-- Never instantiate this class directly! Instead, instantiate an
|
|
|
|
-- implementation of it like DeviceManagerADB. New projects should strongly
|
|
|
|
-- consider using adb.py as an alternative.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- _logcatNeedsRoot = True
|
|
|
|
-- default_timeout = 300
|
|
|
|
-- short_timeout = 30
|
|
|
|
--
|
|
|
|
-- def __init__(self, logLevel=None, deviceRoot=None):
|
|
|
|
-- try:
|
|
|
|
-- self._logger = mozlog.get_default_logger(component="mozdevice")
|
|
|
|
-- if not self._logger: # no global structured logger, fall back to reg logging
|
|
|
|
-- self._logger = mozlog.unstructured.getLogger("mozdevice")
|
|
|
|
-- if logLevel is not None:
|
|
|
|
-- self._logger.setLevel(logLevel)
|
|
|
|
-- except AttributeError:
|
|
|
|
-- # Structured logging doesn't work on Python 2.6
|
|
|
|
-- self._logger = None
|
|
|
|
-- self._logLevel = logLevel
|
|
|
|
-- self._remoteIsWin = None
|
|
|
|
-- self._isDeviceRootSetup = False
|
|
|
|
-- self._deviceRoot = deviceRoot
|
|
|
|
--
|
|
|
|
-- def _log(self, data):
|
|
|
|
-- """
|
|
|
|
-- This helper function is called by ProcessHandler to log
|
|
|
|
-- the output produced by processes
|
|
|
|
-- """
|
|
|
|
-- self._logger.debug(data)
|
|
|
|
--
|
|
|
|
-- @property
|
|
|
|
-- def remoteIsWin(self):
|
|
|
|
-- if self._remoteIsWin is None:
|
|
|
|
-- self._remoteIsWin = self.getInfo("os")["os"][0] == "windows"
|
|
|
|
-- return self._remoteIsWin
|
|
|
|
--
|
|
|
|
-- @property
|
|
|
|
-- def logLevel(self):
|
|
|
|
-- return self._logLevel
|
|
|
|
--
|
|
|
|
-- @logLevel.setter
|
|
|
|
-- def logLevel_setter(self, newLogLevel):
|
|
|
|
-- self._logLevel = newLogLevel
|
|
|
|
-- self._logger.setLevel(self._logLevel)
|
|
|
|
--
|
|
|
|
-- @property
|
|
|
|
-- def debug(self):
|
|
|
|
-- self._logger.warning("dm.debug is deprecated. Use logLevel.")
|
|
|
|
-- levels = {logging.DEBUG: 5, logging.INFO: 3, logging.WARNING: 2,
|
|
|
|
-- logging.ERROR: 1, logging.CRITICAL: 0}
|
|
|
|
-- return levels[self.logLevel]
|
|
|
|
--
|
|
|
|
-- @debug.setter
|
|
|
|
-- def debug_setter(self, newDebug):
|
|
|
|
-- self._logger.warning("dm.debug is deprecated. Use logLevel.")
|
|
|
|
-- newDebug = 5 if newDebug > 5 else newDebug # truncate >=5 to 5
|
|
|
|
-- levels = {5: logging.DEBUG, 3: logging.INFO, 2: logging.WARNING,
|
|
|
|
-- 1: logging.ERROR, 0: logging.CRITICAL}
|
|
|
|
-- self.logLevel = levels[newDebug]
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def getInfo(self, directive=None):
|
|
|
|
-- """
|
|
|
|
-- Returns a dictionary of information strings about the device.
|
|
|
|
--
|
|
|
|
-- :param directive: information you want to get. Options are:
|
|
|
|
--
|
|
|
|
-- - `os` - name of the os
|
|
|
|
-- - `id` - unique id of the device
|
|
|
|
-- - `uptime` - uptime of the device
|
|
|
|
-- - `uptimemillis` - uptime of the device in milliseconds
|
|
|
|
-- (NOT supported on all implementations)
|
|
|
|
-- - `systime` - system time of the device
|
|
|
|
-- - `screen` - screen resolution
|
|
|
|
-- - `memory` - memory stats
|
|
|
|
-- - `memtotal` - total memory available on the device, for example 927208 kB
|
|
|
|
-- - `process` - list of running processes (same as ps)
|
|
|
|
-- - `disk` - total, free, available bytes on disk
|
|
|
|
-- - `power` - power status (charge, battery temp)
|
|
|
|
-- - `temperature` - device temperature
|
|
|
|
--
|
|
|
|
-- If `directive` is `None`, will return all available information
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def getCurrentTime(self):
|
|
|
|
-- """
|
|
|
|
-- Returns device time in milliseconds since the epoch.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- def getIP(self, interfaces=['eth0', 'wlan0']):
|
|
|
|
-- """
|
|
|
|
-- Returns the IP of the device, or None if no connection exists.
|
|
|
|
-- """
|
|
|
|
-- for interface in interfaces:
|
|
|
|
-- match = re.match(r"%s: ip (\S+)" % interface,
|
|
|
|
-- self.shellCheckOutput(['ifconfig', interface],
|
|
|
|
-- timeout=self.short_timeout))
|
|
|
|
-- if match:
|
|
|
|
-- return match.group(1)
|
|
|
|
--
|
|
|
|
-- def recordLogcat(self):
|
|
|
|
-- """
|
|
|
|
-- Clears the logcat file making it easier to view specific events.
|
|
|
|
-- """
|
|
|
|
-- # TODO: spawn this off in a separate thread/process so we can collect all
|
|
|
|
-- # the logcat information
|
|
|
|
--
|
|
|
|
-- # Right now this is just clearing the logcat so we can only see what
|
|
|
|
-- # happens after this call.
|
|
|
|
-- self.shellCheckOutput(['/system/bin/logcat', '-c'], root=self._logcatNeedsRoot,
|
|
|
|
-- timeout=self.short_timeout)
|
|
|
|
--
|
|
|
|
-- def getLogcat(self, filterSpecs=["dalvikvm:I", "ConnectivityService:S",
|
|
|
|
-- "WifiMonitor:S", "WifiStateTracker:S",
|
|
|
|
-- "wpa_supplicant:S", "NetworkStateTracker:S"],
|
|
|
|
-- format="time",
|
|
|
|
-- filterOutRegexps=[]):
|
|
|
|
-- """
|
|
|
|
-- Returns the contents of the logcat file as a list of
|
|
|
|
-- '\n' terminated strings
|
|
|
|
-- """
|
|
|
|
-- cmdline = ["/system/bin/logcat", "-v", format, "-d"] + filterSpecs
|
|
|
|
-- output = self.shellCheckOutput(cmdline,
|
|
|
|
-- root=self._logcatNeedsRoot,
|
|
|
|
-- timeout=self.short_timeout)
|
|
|
|
-- lines = output.replace('\r\n', '\n').splitlines(True)
|
|
|
|
--
|
|
|
|
-- for regex in filterOutRegexps:
|
|
|
|
-- lines = [line for line in lines if not re.search(regex, line)]
|
|
|
|
--
|
|
|
|
-- return lines
|
|
|
|
--
|
|
|
|
-- def saveScreenshot(self, filename):
|
|
|
|
-- """
|
|
|
|
-- Takes a screenshot of what's being display on the device. Uses
|
|
|
|
-- "screencap" on newer (Android 3.0+) devices (and some older ones with
|
|
|
|
-- the functionality backported). This function also works on B2G.
|
|
|
|
--
|
|
|
|
-- Throws an exception on failure. This will always fail on devices
|
|
|
|
-- without the screencap utility.
|
|
|
|
-- """
|
|
|
|
-- screencap = '/system/bin/screencap'
|
|
|
|
-- if not self.fileExists(screencap):
|
|
|
|
-- raise DMError("Unable to capture screenshot on device: no screencap utility")
|
|
|
|
--
|
|
|
|
-- with open(filename, 'w') as pngfile:
|
|
|
|
-- # newer versions of screencap can write directly to a png, but some
|
|
|
|
-- # older versions can't
|
|
|
|
-- tempScreenshotFile = self.deviceRoot + "/ss-dm.tmp"
|
|
|
|
-- self.shellCheckOutput(["sh", "-c", "%s > %s" %
|
|
|
|
-- (screencap, tempScreenshotFile)],
|
|
|
|
-- root=True)
|
|
|
|
-- buf = self.pullFile(tempScreenshotFile)
|
|
|
|
-- width = int(struct.unpack("I", buf[0:4])[0])
|
|
|
|
-- height = int(struct.unpack("I", buf[4:8])[0])
|
|
|
|
-- with open(filename, 'w') as pngfile:
|
|
|
|
-- pngfile.write(self._writePNG(buf[12:], width, height))
|
|
|
|
-- self.removeFile(tempScreenshotFile)
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def pushFile(self, localFilename, remoteFilename, retryLimit=1, createDir=True):
|
|
|
|
-- """
|
|
|
|
-- Copies localname from the host to destname on the device.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def pushDir(self, localDirname, remoteDirname, retryLimit=1, timeout=None):
|
|
|
|
-- """
|
|
|
|
-- Push local directory from host to remote directory on the device,
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def pullFile(self, remoteFilename, offset=None, length=None):
|
|
|
|
-- """
|
|
|
|
-- Returns contents of remoteFile using the "pull" command.
|
|
|
|
--
|
|
|
|
-- :param remoteFilename: Path to file to pull from remote device.
|
|
|
|
-- :param offset: Offset in bytes from which to begin reading (optional)
|
|
|
|
-- :param length: Number of bytes to read (optional)
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def getFile(self, remoteFilename, localFilename):
|
|
|
|
-- """
|
|
|
|
-- Copy file from remote device to local file on host.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def getDirectory(self, remoteDirname, localDirname, checkDir=True):
|
|
|
|
-- """
|
|
|
|
-- Copy directory structure from device (remoteDirname) to host (localDirname).
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def validateFile(self, remoteFilename, localFilename):
|
|
|
|
-- """
|
|
|
|
-- Returns True if a file on the remote device has the same md5 hash as a local one.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- def validateDir(self, localDirname, remoteDirname):
|
|
|
|
-- """
|
|
|
|
-- Returns True if remoteDirname on device is same as localDirname on host.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- self._logger.info("validating directory: %s to %s" % (localDirname, remoteDirname))
|
|
|
|
-- for root, dirs, files in os.walk(localDirname):
|
|
|
|
-- parts = root.split(localDirname)
|
|
|
|
-- for f in files:
|
|
|
|
-- remoteRoot = remoteDirname + '/' + parts[1]
|
|
|
|
-- remoteRoot = remoteRoot.replace('/', '/')
|
|
|
|
-- if (parts[1] == ""):
|
|
|
|
-- remoteRoot = remoteDirname
|
|
|
|
-- remoteName = remoteRoot + '/' + f
|
|
|
|
-- if (self.validateFile(remoteName, os.path.join(root, f)) is not True):
|
|
|
|
-- return False
|
|
|
|
-- return True
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def mkDir(self, remoteDirname):
|
|
|
|
-- """
|
|
|
|
-- Creates a single directory on the device file system.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- def mkDirs(self, filename):
|
|
|
|
-- """
|
|
|
|
-- Make directory structure on the device.
|
|
|
|
--
|
|
|
|
-- WARNING: does not create last part of the path. For example, if asked to
|
|
|
|
-- create `/mnt/sdcard/foo/bar/baz`, it will only create `/mnt/sdcard/foo/bar`
|
|
|
|
-- """
|
|
|
|
-- filename = posixpath.normpath(filename)
|
|
|
|
-- containing = posixpath.dirname(filename)
|
|
|
|
-- if not self.dirExists(containing):
|
|
|
|
-- parts = filename.split('/')
|
|
|
|
-- name = "/" if not self.remoteIsWin else parts.pop(0)
|
|
|
|
-- for part in parts[:-1]:
|
|
|
|
-- if part != "":
|
|
|
|
-- name = posixpath.join(name, part)
|
|
|
|
-- self.mkDir(name) # mkDir will check previous existence
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def dirExists(self, dirpath):
|
|
|
|
-- """
|
|
|
|
-- Returns whether dirpath exists and is a directory on the device file system.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def fileExists(self, filepath):
|
|
|
|
-- """
|
|
|
|
-- Return whether filepath exists on the device file system,
|
|
|
|
-- regardless of file type.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def listFiles(self, rootdir):
|
|
|
|
-- """
|
|
|
|
-- Lists files on the device rootdir.
|
|
|
|
--
|
|
|
|
-- Returns array of filenames, ['file1', 'file2', ...]
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def removeFile(self, filename):
|
|
|
|
-- """
|
|
|
|
-- Removes filename from the device.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def removeDir(self, remoteDirname):
|
|
|
|
-- """
|
|
|
|
-- Does a recursive delete of directory on the device: rm -Rf remoteDirname.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def moveTree(self, source, destination):
|
|
|
|
-- """
|
|
|
|
-- Does a move of the file or directory on the device.
|
|
|
|
--
|
|
|
|
-- :param source: Path to the original file or directory
|
|
|
|
-- :param destination: Path to the destination file or directory
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def copyTree(self, source, destination):
|
|
|
|
-- """
|
|
|
|
-- Does a copy of the file or directory on the device.
|
|
|
|
--
|
|
|
|
-- :param source: Path to the original file or directory
|
|
|
|
-- :param destination: Path to the destination file or directory
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def chmodDir(self, remoteDirname, mask="777"):
|
|
|
|
-- """
|
|
|
|
-- Recursively changes file permissions in a directory.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @property
|
|
|
|
-- def deviceRoot(self):
|
|
|
|
-- """
|
|
|
|
-- The device root on the device filesystem for putting temporary
|
|
|
|
-- testing files.
|
|
|
|
-- """
|
|
|
|
-- # derive deviceroot value if not set
|
|
|
|
-- if not self._deviceRoot or not self._isDeviceRootSetup:
|
|
|
|
-- self._deviceRoot = self._setupDeviceRoot(self._deviceRoot)
|
|
|
|
-- self._isDeviceRootSetup = True
|
|
|
|
--
|
|
|
|
-- return self._deviceRoot
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def _setupDeviceRoot(self):
|
|
|
|
-- """
|
|
|
|
-- Sets up and returns a device root location that can be written to by tests.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- def getDeviceRoot(self):
|
|
|
|
-- """
|
|
|
|
-- Get the device root on the device filesystem for putting temporary
|
|
|
|
-- testing files.
|
|
|
|
--
|
|
|
|
-- .. deprecated:: 0.38
|
|
|
|
-- Use the :py:attr:`deviceRoot` property instead.
|
|
|
|
-- """
|
|
|
|
-- return self.deviceRoot
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def getTempDir(self):
|
|
|
|
-- """
|
|
|
|
-- Returns a temporary directory we can use on this device, ensuring
|
|
|
|
-- also that it exists.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
|
|
|
|
-- """
|
|
|
|
-- Executes shell command on device and returns exit code.
|
|
|
|
--
|
|
|
|
-- :param cmd: Commandline list to execute
|
|
|
|
-- :param outputfile: File to store output
|
|
|
|
-- :param env: Environment to pass to exec command
|
|
|
|
-- :param cwd: Directory to execute command from
|
|
|
|
-- :param timeout: specified in seconds, defaults to 'default_timeout'
|
|
|
|
-- :param root: Specifies whether command requires root privileges
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- def shellCheckOutput(self, cmd, env=None, cwd=None, timeout=None, root=False):
|
|
|
|
-- """
|
|
|
|
-- Executes shell command on device and returns output as a string. Raises if
|
|
|
|
-- the return code is non-zero.
|
|
|
|
--
|
|
|
|
-- :param cmd: Commandline list to execute
|
|
|
|
-- :param env: Environment to pass to exec command
|
|
|
|
-- :param cwd: Directory to execute command from
|
|
|
|
-- :param timeout: specified in seconds, defaults to 'default_timeout'
|
|
|
|
-- :param root: Specifies whether command requires root privileges
|
|
|
|
-- :raises: DMError
|
|
|
|
-- """
|
|
|
|
-- buf = StringIO.StringIO()
|
|
|
|
-- retval = self.shell(cmd, buf, env=env, cwd=cwd, timeout=timeout, root=root)
|
|
|
|
-- output = str(buf.getvalue()[0:-1]).rstrip()
|
|
|
|
-- buf.close()
|
|
|
|
-- if retval != 0:
|
|
|
|
-- raise DMError(
|
|
|
|
-- "Non-zero return code for command: %s "
|
|
|
|
-- "(output: '%s', retval: '%s')" % (cmd, output, retval))
|
|
|
|
-- return output
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def getProcessList(self):
|
|
|
|
-- """
|
|
|
|
-- Returns array of tuples representing running processes on the device.
|
|
|
|
--
|
|
|
|
-- Format of tuples is (processId, processName, userId)
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- def processInfo(self, processName):
|
|
|
|
-- """
|
|
|
|
-- Returns information on the process with processName.
|
|
|
|
-- Information on process is in tuple format: (pid, process path, user)
|
|
|
|
-- If a process with the specified name does not exist this function will return None.
|
|
|
|
-- """
|
|
|
|
-- if not isinstance(processName, basestring):
|
|
|
|
-- raise TypeError("Process name %s is not a string" % processName)
|
|
|
|
--
|
|
|
|
-- processInfo = None
|
|
|
|
--
|
|
|
|
-- # filter out extra spaces
|
|
|
|
-- parts = filter(lambda x: x != '', processName.split(' '))
|
|
|
|
-- processName = ' '.join(parts)
|
|
|
|
--
|
|
|
|
-- # filter out the quoted env string if it exists
|
|
|
|
-- # ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
|
|
|
|
-- parts = processName.split('"')
|
|
|
|
-- if (len(parts) > 2):
|
|
|
|
-- processName = ' '.join(parts[2:]).strip()
|
|
|
|
--
|
|
|
|
-- pieces = processName.split(' ')
|
|
|
|
-- parts = pieces[0].split('/')
|
|
|
|
-- app = parts[-1]
|
|
|
|
--
|
|
|
|
-- procList = self.getProcessList()
|
|
|
|
-- if (procList == []):
|
|
|
|
-- return None
|
|
|
|
--
|
|
|
|
-- for proc in procList:
|
|
|
|
-- procName = proc[1].split('/')[-1]
|
|
|
|
-- if (procName == app):
|
|
|
|
-- processInfo = proc
|
|
|
|
-- break
|
|
|
|
-- return processInfo
|
|
|
|
--
|
|
|
|
-- def processExist(self, processName):
|
|
|
|
-- """
|
|
|
|
-- Returns True if process with name processName is running on device.
|
|
|
|
-- """
|
|
|
|
-- processInfo = self.processInfo(processName)
|
|
|
|
-- if processInfo:
|
|
|
|
-- return processInfo[0]
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def killProcess(self, processName, sig=None):
|
|
|
|
-- """
|
|
|
|
-- Kills the process named processName. If sig is not None, process is
|
|
|
|
-- killed with the specified signal.
|
|
|
|
--
|
|
|
|
-- :param processName: path or name of the process to kill
|
|
|
|
-- :param sig: signal to pass into the kill command (optional)
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def reboot(self, wait=False, ipAddr=None):
|
|
|
|
-- """
|
|
|
|
-- Reboots the device.
|
|
|
|
--
|
|
|
|
-- :param wait: block on device to come back up before returning
|
|
|
|
-- :param ipAddr: deprecated; do not use
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def installApp(self, appBundlePath, destPath=None):
|
|
|
|
-- """
|
|
|
|
-- Installs an application onto the device.
|
|
|
|
--
|
|
|
|
-- :param appBundlePath: path to the application bundle on the device
|
|
|
|
-- :param destPath: destination directory of where application should be
|
|
|
|
-- installed to (optional)
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def uninstallApp(self, appName, installPath=None):
|
|
|
|
-- """
|
|
|
|
-- Uninstalls the named application from device and DOES NOT cause a reboot.
|
|
|
|
--
|
|
|
|
-- :param appName: the name of the application (e.g org.mozilla.fennec)
|
|
|
|
-- :param installPath: the path to where the application was installed (optional)
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def uninstallAppAndReboot(self, appName, installPath=None):
|
|
|
|
-- """
|
|
|
|
-- Uninstalls the named application from device and causes a reboot.
|
|
|
|
--
|
|
|
|
-- :param appName: the name of the application (e.g org.mozilla.fennec)
|
|
|
|
-- :param installPath: the path to where the application was installed (optional)
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def updateApp(self, appBundlePath, processName=None, destPath=None,
|
|
|
|
-- wait=False, ipAddr=None):
|
|
|
|
-- """
|
|
|
|
-- Updates the application on the device and reboots.
|
|
|
|
--
|
|
|
|
-- :param appBundlePath: path to the application bundle on the device
|
|
|
|
-- :param processName: used to end the process if the applicaiton is
|
|
|
|
-- currently running (optional)
|
|
|
|
-- :param destPath: Destination directory to where the application should
|
|
|
|
-- be installed (optional)
|
|
|
|
-- :param wait: block on device to come back up before returning
|
|
|
|
-- :param ipAddr: deprecated; do not use
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @staticmethod
|
|
|
|
-- def _writePNG(buf, width, height):
|
|
|
|
-- """
|
|
|
|
-- Method for writing a PNG from a buffer, used by getScreenshot on older devices,
|
|
|
|
-- """
|
|
|
|
-- # Based on: http://code.activestate.com/recipes/577443-write-a-png-image-in-native-python/
|
|
|
|
-- width_byte_4 = width * 4
|
|
|
|
-- raw_data = b"".join(b'\x00' + buf[span:span + width_byte_4]
|
|
|
|
-- for span in range(0, (height - 1) * width * 4, width_byte_4))
|
|
|
|
--
|
|
|
|
-- def png_pack(png_tag, data):
|
|
|
|
-- chunk_head = png_tag + data
|
|
|
|
-- return struct.pack("!I", len(data)) \
|
|
|
|
-- + chunk_head \
|
|
|
|
-- + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
|
|
|
|
-- return b"".join([
|
|
|
|
-- b'\x89PNG\r\n\x1a\n',
|
|
|
|
-- png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
|
|
|
|
-- png_pack(b'IDAT', zlib.compress(raw_data, 9)),
|
|
|
|
-- png_pack(b'IEND', b'')])
|
|
|
|
--
|
|
|
|
-- @abstractmethod
|
|
|
|
-- def _getRemoteHash(self, filename):
|
|
|
|
-- """
|
|
|
|
-- Return the md5 sum of a file on the device.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- @staticmethod
|
|
|
|
-- def _getLocalHash(filename):
|
|
|
|
-- """
|
|
|
|
-- Return the MD5 sum of a file on the host.
|
|
|
|
-- """
|
|
|
|
-- f = open(filename, 'rb')
|
|
|
|
-- if f is None:
|
|
|
|
-- return None
|
|
|
|
--
|
|
|
|
-- try:
|
|
|
|
-- mdsum = hashlib.md5()
|
|
|
|
-- except Exception:
|
|
|
|
-- return None
|
|
|
|
--
|
|
|
|
-- while 1:
|
|
|
|
-- data = f.read(1024)
|
|
|
|
-- if not data:
|
|
|
|
-- break
|
|
|
|
-- mdsum.update(data)
|
|
|
|
--
|
|
|
|
-- f.close()
|
|
|
|
-- hexval = mdsum.hexdigest()
|
|
|
|
-- return hexval
|
|
|
|
--
|
|
|
|
-- @staticmethod
|
|
|
|
-- def _escapedCommandLine(cmd):
|
|
|
|
-- """
|
|
|
|
-- Utility function to return escaped and quoted version of command line.
|
|
|
|
-- """
|
|
|
|
-- quotedCmd = []
|
|
|
|
--
|
|
|
|
-- for arg in cmd:
|
|
|
|
-- arg.replace('&', '\&')
|
|
|
|
--
|
|
|
|
-- needsQuoting = False
|
|
|
|
-- for char in [' ', '(', ')', '"', '&']:
|
|
|
|
-- if arg.find(char) >= 0:
|
|
|
|
-- needsQuoting = True
|
|
|
|
-- break
|
|
|
|
-- if needsQuoting:
|
|
|
|
-- arg = '\'%s\'' % arg
|
|
|
|
--
|
|
|
|
-- quotedCmd.append(arg)
|
|
|
|
--
|
|
|
|
-- return " ".join(quotedCmd)
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--def _pop_last_line(file_obj):
|
|
|
|
-- """
|
|
|
|
-- Utility function to get the last line from a file. Function also removes
|
|
|
|
-- it from the file. Intended to strip off the return code from a shell
|
|
|
|
-- command.
|
|
|
|
-- """
|
|
|
|
-- bytes_from_end = 1
|
|
|
|
-- file_obj.seek(0, 2)
|
|
|
|
-- length = file_obj.tell() + 1
|
|
|
|
-- while bytes_from_end < length:
|
|
|
|
-- file_obj.seek((-1) * bytes_from_end, 2)
|
|
|
|
-- data = file_obj.read()
|
|
|
|
--
|
|
|
|
-- if bytes_from_end == length - 1 and len(data) == 0: # no data, return None
|
|
|
|
-- return None
|
|
|
|
--
|
|
|
|
-- if data[0] == '\n' or bytes_from_end == length - 1:
|
|
|
|
-- # found the last line, which should have the return value
|
|
|
|
-- if data[0] == '\n':
|
|
|
|
-- data = data[1:]
|
|
|
|
--
|
|
|
|
-- # truncate off the return code line
|
|
|
|
-- file_obj.truncate(length - bytes_from_end)
|
|
|
|
-- file_obj.seek(0, 2)
|
|
|
|
-- file_obj.write('\0')
|
|
|
|
--
|
|
|
|
-- return data
|
|
|
|
--
|
|
|
|
-- bytes_from_end += 1
|
|
|
|
--
|
|
|
|
-- return None
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
|
|
|
|
-deleted file mode 100644
|
|
|
|
---- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
|
|
|
|
-+++ /dev/null
|
|
|
|
-@@ -1,886 +0,0 @@
|
|
|
|
--# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
--# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
--# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
--
|
|
|
|
--from __future__ import absolute_import
|
|
|
|
--
|
|
|
|
--import logging
|
|
|
|
--import re
|
|
|
|
--import os
|
|
|
|
--import tempfile
|
|
|
|
--import time
|
|
|
|
--import traceback
|
|
|
|
--
|
|
|
|
--from distutils import dir_util
|
|
|
|
--
|
|
|
|
--from .devicemanager import DeviceManager, DMError
|
|
|
|
--from mozprocess import ProcessHandler
|
|
|
|
--import mozfile
|
|
|
|
--from . import version_codes
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class DeviceManagerADB(DeviceManager):
|
|
|
|
-- """
|
|
|
|
-- Implementation of DeviceManager interface that uses the Android "adb"
|
|
|
|
-- utility to communicate with the device. Normally used to communicate
|
|
|
|
-- with a device that is directly connected with the host machine over a USB
|
|
|
|
-- port.
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- _haveRootShell = None
|
|
|
|
-- _haveSu = None
|
|
|
|
-- _suModifier = None
|
|
|
|
-- _lsModifier = None
|
|
|
|
-- _useZip = False
|
|
|
|
-- _logcatNeedsRoot = False
|
|
|
|
-- _pollingInterval = 0.01
|
|
|
|
-- _packageName = None
|
|
|
|
-- _tempDir = None
|
|
|
|
-- _adb_version = None
|
|
|
|
-- _sdk_version = None
|
|
|
|
-- connected = False
|
|
|
|
--
|
|
|
|
-- def __init__(self, host=None, port=5555, retryLimit=5, packageName='fennec',
|
|
|
|
-- adbPath=None, deviceSerial=None, deviceRoot=None,
|
|
|
|
-- logLevel=logging.ERROR, autoconnect=True, runAdbAsRoot=False,
|
|
|
|
-- serverHost=None, serverPort=None, **kwargs):
|
|
|
|
-- DeviceManager.__init__(self, logLevel=logLevel,
|
|
|
|
-- deviceRoot=deviceRoot)
|
|
|
|
-- self.host = host
|
|
|
|
-- self.port = port
|
|
|
|
-- self.retryLimit = retryLimit
|
|
|
|
--
|
|
|
|
-- self._serverHost = serverHost
|
|
|
|
-- self._serverPort = serverPort
|
|
|
|
--
|
|
|
|
-- # the path to adb, or 'adb' to assume that it's on the PATH
|
|
|
|
-- self._adbPath = adbPath or 'adb'
|
|
|
|
--
|
|
|
|
-- # The serial number of the device to use with adb, used in cases
|
|
|
|
-- # where multiple devices are being managed by the same adb instance.
|
|
|
|
-- self._deviceSerial = deviceSerial
|
|
|
|
--
|
|
|
|
-- # Some devices do no start adb as root, if allowed you can use
|
|
|
|
-- # this to reboot adbd on the device as root automatically
|
|
|
|
-- self._runAdbAsRoot = runAdbAsRoot
|
|
|
|
--
|
|
|
|
-- if packageName == 'fennec':
|
|
|
|
-- if os.getenv('USER'):
|
|
|
|
-- self._packageName = 'org.mozilla.fennec_' + os.getenv('USER')
|
|
|
|
-- else:
|
|
|
|
-- self._packageName = 'org.mozilla.fennec_'
|
|
|
|
-- elif packageName:
|
|
|
|
-- self._packageName = packageName
|
|
|
|
--
|
|
|
|
-- # verify that we can run the adb command. can't continue otherwise
|
|
|
|
-- self._verifyADB()
|
|
|
|
--
|
|
|
|
-- if autoconnect:
|
|
|
|
-- self.connect()
|
|
|
|
--
|
|
|
|
-- def connect(self):
|
|
|
|
-- if not self.connected:
|
|
|
|
-- # try to connect to the device over tcp/ip if we have a hostname
|
|
|
|
-- if self.host:
|
|
|
|
-- self._connectRemoteADB()
|
|
|
|
--
|
|
|
|
-- # verify that we can connect to the device. can't continue
|
|
|
|
-- self._verifyDevice()
|
|
|
|
--
|
|
|
|
-- # Note SDK version
|
|
|
|
-- try:
|
|
|
|
-- proc = self._runCmd(["shell", "getprop", "ro.build.version.sdk"],
|
|
|
|
-- timeout=self.short_timeout)
|
|
|
|
-- self._sdk_version = int(proc.output[0])
|
|
|
|
-- except (OSError, ValueError):
|
|
|
|
-- self._sdk_version = 0
|
|
|
|
-- self._logger.info("Detected Android sdk %d" % self._sdk_version)
|
|
|
|
--
|
|
|
|
-- # Some commands require root to work properly, even with ADB (e.g.
|
|
|
|
-- # grabbing APKs out of /data). For these cases, we check whether
|
|
|
|
-- # we're running as root. If that isn't true, check for the
|
|
|
|
-- # existence of an su binary
|
|
|
|
-- self._checkForRoot()
|
|
|
|
--
|
|
|
|
-- # can we use zip to speed up some file operations? (currently not
|
|
|
|
-- # required)
|
|
|
|
-- try:
|
|
|
|
-- self._verifyZip()
|
|
|
|
-- except DMError:
|
|
|
|
-- pass
|
|
|
|
--
|
|
|
|
-- def __del__(self):
|
|
|
|
-- if self.host:
|
|
|
|
-- self._disconnectRemoteADB()
|
|
|
|
--
|
|
|
|
-- def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
|
|
|
|
-- # FIXME: this function buffers all output of the command into memory,
|
|
|
|
-- # always. :(
|
|
|
|
--
|
|
|
|
-- # If requested to run as root, check that we can actually do that
|
|
|
|
-- if root:
|
|
|
|
-- if self._haveRootShell is None and self._haveSu is None:
|
|
|
|
-- self._checkForRoot()
|
|
|
|
-- if not self._haveRootShell and not self._haveSu:
|
|
|
|
-- raise DMError(
|
|
|
|
-- "Shell command '%s' requested to run as root but root "
|
|
|
|
-- "is not available on this device. Root your device or "
|
|
|
|
-- "refactor the test/harness to not require root." %
|
|
|
|
-- self._escapedCommandLine(cmd))
|
|
|
|
--
|
|
|
|
-- # Getting the return code is more complex than you'd think because adb
|
|
|
|
-- # doesn't actually return the return code from a process, so we have to
|
|
|
|
-- # capture the output to get it
|
|
|
|
-- if root and self._haveSu:
|
|
|
|
-- cmdline = "su %s \"%s\"" % (self._suModifier,
|
|
|
|
-- self._escapedCommandLine(cmd))
|
|
|
|
-- else:
|
|
|
|
-- cmdline = self._escapedCommandLine(cmd)
|
|
|
|
-- cmdline += "; echo $?"
|
|
|
|
--
|
|
|
|
-- # prepend cwd and env to command if necessary
|
|
|
|
-- if cwd:
|
|
|
|
-- cmdline = "cd %s; %s" % (cwd, cmdline)
|
|
|
|
-- if env:
|
|
|
|
-- envstr = '; '.join(map(lambda x: 'export %s=%s' % (x[0], x[1]), env.iteritems()))
|
|
|
|
-- cmdline = envstr + "; " + cmdline
|
|
|
|
--
|
|
|
|
-- # all output should be in stdout
|
|
|
|
-- args = [self._adbPath]
|
|
|
|
-- if self._serverHost is not None:
|
|
|
|
-- args.extend(['-H', self._serverHost])
|
|
|
|
-- if self._serverPort is not None:
|
|
|
|
-- args.extend(['-P', str(self._serverPort)])
|
|
|
|
-- if self._deviceSerial:
|
|
|
|
-- args.extend(['-s', self._deviceSerial])
|
|
|
|
-- args.extend(["shell", cmdline])
|
|
|
|
--
|
|
|
|
-- def _timeout():
|
|
|
|
-- self._logger.error("Timeout exceeded for shell call '%s'" % ' '.join(args))
|
|
|
|
--
|
|
|
|
-- self._logger.debug("shell - command: %s" % ' '.join(args))
|
|
|
|
-- proc = ProcessHandler(args, processOutputLine=self._log, onTimeout=_timeout)
|
|
|
|
--
|
|
|
|
-- if not timeout:
|
|
|
|
-- # We are asserting that all commands will complete in this time unless
|
|
|
|
-- # otherwise specified
|
|
|
|
-- timeout = self.default_timeout
|
|
|
|
--
|
|
|
|
-- timeout = int(timeout)
|
|
|
|
-- proc.run(timeout)
|
|
|
|
-- proc.wait()
|
|
|
|
-- output = proc.output
|
|
|
|
--
|
|
|
|
-- if output:
|
|
|
|
-- lastline = output[-1]
|
|
|
|
-- if lastline:
|
|
|
|
-- m = re.search('([0-9]+)', lastline)
|
|
|
|
-- if m:
|
|
|
|
-- return_code = m.group(1)
|
|
|
|
-- for line in output:
|
|
|
|
-- outputfile.write(line + '\n')
|
|
|
|
-- outputfile.seek(-2, 2)
|
|
|
|
-- outputfile.truncate() # truncate off the return code
|
|
|
|
-- return int(return_code)
|
|
|
|
--
|
|
|
|
-- return None
|
|
|
|
--
|
|
|
|
-- def forward(self, local, remote):
|
|
|
|
-- """
|
|
|
|
-- Forward socket connections.
|
|
|
|
--
|
|
|
|
-- Forward specs are one of:
|
|
|
|
-- tcp:<port>
|
|
|
|
-- localabstract:<unix domain socket name>
|
|
|
|
-- localreserved:<unix domain socket name>
|
|
|
|
-- localfilesystem:<unix domain socket name>
|
|
|
|
-- dev:<character device name>
|
|
|
|
-- jdwp:<process pid> (remote only)
|
|
|
|
-- """
|
|
|
|
-- if not self._checkCmd(['forward', local, remote], timeout=self.short_timeout) == 0:
|
|
|
|
-- raise DMError("Failed to forward socket connection.")
|
|
|
|
--
|
|
|
|
-- def remove_forward(self, local=None):
|
|
|
|
-- """
|
|
|
|
-- Turn off forwarding of socket connection.
|
|
|
|
-- """
|
|
|
|
-- cmd = ['forward']
|
|
|
|
-- if local is None:
|
|
|
|
-- cmd.extend(['--remove-all'])
|
|
|
|
-- else:
|
|
|
|
-- cmd.extend(['--remove', local])
|
|
|
|
-- if not self._checkCmd(cmd, timeout=self.short_timeout) == 0:
|
|
|
|
-- raise DMError("Failed to remove connection forwarding.")
|
|
|
|
--
|
|
|
|
-- def remount(self):
|
|
|
|
-- "Remounts the /system partition on the device read-write."
|
|
|
|
-- return self._checkCmd(['remount'], timeout=self.short_timeout)
|
|
|
|
--
|
|
|
|
-- def devices(self):
|
|
|
|
-- "Return a list of connected devices as (serial, status) tuples."
|
|
|
|
-- proc = self._runCmd(['devices'])
|
|
|
|
-- proc.output.pop(0) # ignore first line of output
|
|
|
|
-- devices = []
|
|
|
|
-- for line in proc.output:
|
|
|
|
-- result = re.match('(.*?)\t(.*)', line)
|
|
|
|
-- if result:
|
|
|
|
-- devices.append((result.group(1), result.group(2)))
|
|
|
|
-- return devices
|
|
|
|
--
|
|
|
|
-- def _connectRemoteADB(self):
|
|
|
|
-- self._checkCmd(["connect", self.host + ":" + str(self.port)])
|
|
|
|
--
|
|
|
|
-- def _disconnectRemoteADB(self):
|
|
|
|
-- self._checkCmd(["disconnect", self.host + ":" + str(self.port)])
|
|
|
|
--
|
|
|
|
-- def pushFile(self, localname, destname, retryLimit=None, createDir=True):
|
|
|
|
-- # you might expect us to put the file *in* the directory in this case,
|
|
|
|
-- # but that would be inconsistent with historical behavior.
|
|
|
|
-- retryLimit = retryLimit or self.retryLimit
|
|
|
|
-- if self.dirExists(destname):
|
|
|
|
-- raise DMError("Attempted to push a file (%s) to a directory (%s)!" %
|
|
|
|
-- (localname, destname))
|
|
|
|
-- if not os.access(localname, os.F_OK):
|
|
|
|
-- raise DMError("File not found: %s" % localname)
|
|
|
|
--
|
|
|
|
-- proc = self._runCmd(["push", os.path.realpath(localname), destname],
|
|
|
|
-- retryLimit=retryLimit)
|
|
|
|
-- if proc.returncode != 0:
|
|
|
|
-- raise DMError("Error pushing file %s -> %s; output: %s" %
|
|
|
|
-- (localname, destname, proc.output))
|
|
|
|
--
|
|
|
|
-- def mkDir(self, name):
|
|
|
|
-- result = self._runCmd(["shell", "mkdir", name], timeout=self.short_timeout).output
|
|
|
|
-- if len(result) and 'read-only file system' in result[0].lower():
|
|
|
|
-- raise DMError("Error creating directory: read only file system")
|
|
|
|
--
|
|
|
|
-- def pushDir(self, localDir, remoteDir, retryLimit=None, timeout=None):
|
|
|
|
-- # adb "push" accepts a directory as an argument, but if the directory
|
|
|
|
-- # contains symbolic links, the links are pushed, rather than the linked
|
|
|
|
-- # files; we either zip/unzip or re-copy the directory into a temporary
|
|
|
|
-- # one to get around this limitation
|
|
|
|
-- retryLimit = retryLimit or self.retryLimit
|
|
|
|
-- if self._useZip:
|
|
|
|
-- self.removeDir(remoteDir)
|
|
|
|
-- self.mkDirs(remoteDir + "/x")
|
|
|
|
-- try:
|
|
|
|
-- localZip = tempfile.mktemp() + ".zip"
|
|
|
|
-- remoteZip = remoteDir + "/adbdmtmp.zip"
|
|
|
|
-- proc = ProcessHandler(["zip", "-r", localZip, '.'], cwd=localDir,
|
|
|
|
-- processOutputLine=self._log)
|
|
|
|
-- proc.run()
|
|
|
|
-- proc.wait()
|
|
|
|
-- self.pushFile(localZip, remoteZip, retryLimit=retryLimit, createDir=False)
|
|
|
|
-- mozfile.remove(localZip)
|
|
|
|
-- data = self._runCmd(["shell", "unzip", "-o", remoteZip,
|
|
|
|
-- "-d", remoteDir]).output[0]
|
|
|
|
-- self._checkCmd(["shell", "rm", remoteZip],
|
|
|
|
-- retryLimit=retryLimit, timeout=self.short_timeout)
|
|
|
|
-- if re.search("unzip: exiting", data) or re.search("Operation not permitted", data):
|
|
|
|
-- raise Exception("unzip failed, or permissions error")
|
|
|
|
-- except Exception:
|
|
|
|
-- self._logger.warning(traceback.format_exc())
|
|
|
|
-- self._logger.warning("zip/unzip failure: falling back to normal push")
|
|
|
|
-- self._useZip = False
|
|
|
|
-- self.pushDir(localDir, remoteDir, retryLimit=retryLimit, timeout=timeout)
|
|
|
|
-- else:
|
|
|
|
-- localDir = os.path.normpath(localDir)
|
|
|
|
-- remoteDir = os.path.normpath(remoteDir)
|
|
|
|
-- tempParent = tempfile.mkdtemp()
|
|
|
|
-- remoteName = os.path.basename(remoteDir)
|
|
|
|
-- newLocal = os.path.join(tempParent, remoteName)
|
|
|
|
-- dir_util.copy_tree(localDir, newLocal)
|
|
|
|
-- # See do_sync_push in
|
|
|
|
-- # https://android.googlesource.com/platform/system/core/+/master/adb/file_sync_client.cpp
|
|
|
|
-- # Work around change in behavior in adb 1.0.36 where if
|
|
|
|
-- # the remote destination directory exists, adb push will
|
|
|
|
-- # copy the source directory *into* the destination
|
|
|
|
-- # directory otherwise it will copy the source directory
|
|
|
|
-- # *onto* the destination directory.
|
|
|
|
-- if self._adb_version >= '1.0.36':
|
|
|
|
-- remoteDir = '/'.join(remoteDir.rstrip('/').split('/')[:-1])
|
|
|
|
-- try:
|
|
|
|
-- if self._checkCmd(["push", newLocal, remoteDir],
|
|
|
|
-- retryLimit=retryLimit, timeout=timeout):
|
|
|
|
-- raise DMError("failed to push %s (copy of %s) to %s" %
|
|
|
|
-- (newLocal, localDir, remoteDir))
|
|
|
|
-- except BaseException:
|
|
|
|
-- raise
|
|
|
|
-- finally:
|
|
|
|
-- mozfile.remove(tempParent)
|
|
|
|
--
|
|
|
|
-- def dirExists(self, remotePath):
|
|
|
|
-- self._detectLsModifier()
|
|
|
|
-- data = self._runCmd(["shell", "ls", self._lsModifier, remotePath + '/'],
|
|
|
|
-- timeout=self.short_timeout).output
|
|
|
|
--
|
|
|
|
-- if len(data) == 1:
|
|
|
|
-- res = data[0]
|
|
|
|
-- if "Not a directory" in res or "No such file or directory" in res:
|
|
|
|
-- return False
|
|
|
|
-- return True
|
|
|
|
--
|
|
|
|
-- def fileExists(self, filepath):
|
|
|
|
-- self._detectLsModifier()
|
|
|
|
-- data = self._runCmd(["shell", "ls", self._lsModifier, filepath],
|
|
|
|
-- timeout=self.short_timeout).output
|
|
|
|
-- if len(data) == 1:
|
|
|
|
-- foundpath = data[0].decode('utf-8').rstrip()
|
|
|
|
-- if foundpath == filepath:
|
|
|
|
-- return True
|
|
|
|
-- return False
|
|
|
|
--
|
|
|
|
-- def removeFile(self, filename):
|
|
|
|
-- if self.fileExists(filename):
|
|
|
|
-- self._checkCmd(["shell", "rm", filename], timeout=self.short_timeout)
|
|
|
|
--
|
|
|
|
-- def removeDir(self, remoteDir):
|
|
|
|
-- if self.dirExists(remoteDir):
|
|
|
|
-- self._checkCmd(["shell", "rm", "-r", remoteDir], timeout=self.short_timeout)
|
|
|
|
-- else:
|
|
|
|
-- self.removeFile(remoteDir.strip())
|
|
|
|
--
|
|
|
|
-- def moveTree(self, source, destination):
|
|
|
|
-- self._checkCmd(["shell", "mv", source, destination], timeout=self.short_timeout)
|
|
|
|
--
|
|
|
|
-- def copyTree(self, source, destination):
|
|
|
|
-- self._checkCmd(["shell", "dd", "if=%s" % source, "of=%s" % destination])
|
|
|
|
--
|
|
|
|
-- def listFiles(self, rootdir):
|
|
|
|
-- self._detectLsModifier()
|
|
|
|
-- data = self._runCmd(["shell", "ls", self._lsModifier, rootdir],
|
|
|
|
-- timeout=self.short_timeout).output
|
|
|
|
-- data[:] = [item.rstrip('\r\n') for item in data]
|
|
|
|
-- if (len(data) == 1):
|
|
|
|
-- if (data[0] == rootdir):
|
|
|
|
-- return []
|
|
|
|
-- if (data[0].find("No such file or directory") != -1):
|
|
|
|
-- return []
|
|
|
|
-- if (data[0].find("Not a directory") != -1):
|
|
|
|
-- return []
|
|
|
|
-- if (data[0].find("Permission denied") != -1):
|
|
|
|
-- return []
|
|
|
|
-- if (data[0].find("opendir failed") != -1):
|
|
|
|
-- return []
|
|
|
|
-- if (data[0].find("Device or resource busy") != -1):
|
|
|
|
-- return []
|
|
|
|
-- return data
|
|
|
|
--
|
|
|
|
-- def getProcessList(self):
|
|
|
|
-- ret = []
|
|
|
|
-- p = self._runCmd(["shell", "ps"], timeout=self.short_timeout)
|
|
|
|
-- if not p or not p.output or len(p.output) < 1:
|
|
|
|
-- return ret
|
|
|
|
-- # first line is the headers
|
|
|
|
-- p.output.pop(0)
|
|
|
|
-- for proc in p.output:
|
|
|
|
-- els = proc.split()
|
|
|
|
-- # We need to figure out if this is "user pid name" or
|
|
|
|
-- # "pid user vsz stat command"
|
|
|
|
-- if els[1].isdigit():
|
|
|
|
-- ret.append(list([int(els[1]), els[len(els) - 1], els[0]]))
|
|
|
|
-- else:
|
|
|
|
-- ret.append(list([int(els[0]), els[len(els) - 1], els[1]]))
|
|
|
|
-- return ret
|
|
|
|
--
|
|
|
|
-- def fireProcess(self, appname, failIfRunning=False):
|
|
|
|
-- """
|
|
|
|
-- Starts a process
|
|
|
|
--
|
|
|
|
-- returns: pid
|
|
|
|
--
|
|
|
|
-- DEPRECATED: Use shell() or launchApplication() for new code
|
|
|
|
-- """
|
|
|
|
-- # strip out env vars
|
|
|
|
-- parts = appname.split('"')
|
|
|
|
-- if (len(parts) > 2):
|
|
|
|
-- parts = parts[2:]
|
|
|
|
-- return self.launchProcess(parts, failIfRunning)
|
|
|
|
--
|
|
|
|
-- def launchProcess(self, cmd, outputFile="process.txt", cwd='', env='', failIfRunning=False):
|
|
|
|
-- """
|
|
|
|
-- Launches a process, redirecting output to standard out
|
|
|
|
--
|
|
|
|
-- WARNING: Does not work how you expect on Android! The application's
|
|
|
|
-- own output will be flushed elsewhere.
|
|
|
|
--
|
|
|
|
-- DEPRECATED: Use shell() or launchApplication() for new code
|
|
|
|
-- """
|
|
|
|
-- if cmd[0] == "am":
|
|
|
|
-- self._checkCmd(["shell"] + cmd)
|
|
|
|
-- return outputFile
|
|
|
|
--
|
|
|
|
-- acmd = ["-W"]
|
|
|
|
-- cmd = ' '.join(cmd).strip()
|
|
|
|
-- i = cmd.find(" ")
|
|
|
|
-- re_url = re.compile('^[http|file|chrome|about].*')
|
|
|
|
-- last = cmd.rfind(" ")
|
|
|
|
-- uri = ""
|
|
|
|
-- args = ""
|
|
|
|
-- if re_url.match(cmd[last:].strip()):
|
|
|
|
-- args = cmd[i:last].strip()
|
|
|
|
-- uri = cmd[last:].strip()
|
|
|
|
-- else:
|
|
|
|
-- args = cmd[i:].strip()
|
|
|
|
-- acmd.append("-n")
|
|
|
|
-- acmd.append(cmd[0:i] + "/org.mozilla.gecko.BrowserApp")
|
|
|
|
-- if args != "":
|
|
|
|
-- acmd.append("--es")
|
|
|
|
-- acmd.append("args")
|
|
|
|
-- acmd.append(args)
|
|
|
|
-- if env != '' and env is not None:
|
|
|
|
-- envCnt = 0
|
|
|
|
-- # env is expected to be a dict of environment variables
|
|
|
|
-- for envkey, envval in env.iteritems():
|
|
|
|
-- acmd.append("--es")
|
|
|
|
-- acmd.append("env" + str(envCnt))
|
|
|
|
-- acmd.append(envkey + "=" + envval)
|
|
|
|
-- envCnt += 1
|
|
|
|
-- if uri != "":
|
|
|
|
-- acmd.append("-d")
|
|
|
|
-- acmd.append(uri)
|
|
|
|
--
|
|
|
|
-- acmd = ["shell", ' '.join(map(lambda x: '"' + x + '"', ["am", "start"] + acmd))]
|
|
|
|
-- self._logger.info(acmd)
|
|
|
|
-- self._checkCmd(acmd)
|
|
|
|
-- return outputFile
|
|
|
|
--
|
|
|
|
-- def killProcess(self, appname, sig=None):
|
|
|
|
-- try:
|
|
|
|
-- self.shellCheckOutput(["am", "force-stop", appname], timeout=self.short_timeout)
|
|
|
|
-- except Exception:
|
|
|
|
-- # no problem - will kill it instead
|
|
|
|
-- self._logger.info("killProcess failed force-stop of %s" % appname)
|
|
|
|
--
|
|
|
|
-- shell_args = ["shell"]
|
|
|
|
-- if self._sdk_version >= version_codes.N:
|
|
|
|
-- # Bug 1334613 - force use of root
|
|
|
|
-- if self._haveRootShell is None and self._haveSu is None:
|
|
|
|
-- self._checkForRoot()
|
|
|
|
-- if not self._haveRootShell and not self._haveSu:
|
|
|
|
-- raise DMError(
|
|
|
|
-- "killProcess '%s' requested to run as root but root "
|
|
|
|
-- "is not available on this device. Root your device or "
|
|
|
|
-- "refactor the test/harness to not require root." %
|
|
|
|
-- appname)
|
|
|
|
-- if not self._haveRootShell:
|
|
|
|
-- shell_args.extend(["su", self._suModifier])
|
|
|
|
--
|
|
|
|
-- procs = self.getProcessList()
|
|
|
|
-- for (pid, name, user) in procs:
|
|
|
|
-- if name == appname:
|
|
|
|
-- args = list(shell_args)
|
|
|
|
-- args.append("kill")
|
|
|
|
-- if sig:
|
|
|
|
-- args.append("-%d" % sig)
|
|
|
|
-- args.append(str(pid))
|
|
|
|
-- p = self._runCmd(args, timeout=self.short_timeout)
|
|
|
|
-- if p.returncode != 0 and len(p.output) > 0 and \
|
|
|
|
-- 'No such process' not in p.output[0]:
|
|
|
|
-- raise DMError("Error killing process "
|
|
|
|
-- "'%s': %s" % (appname, p.output))
|
|
|
|
--
|
|
|
|
-- def _runPull(self, remoteFile, localFile):
|
|
|
|
-- """
|
|
|
|
-- Pulls remoteFile from device to host
|
|
|
|
-- """
|
|
|
|
-- try:
|
|
|
|
-- self._runCmd(["pull", remoteFile, localFile])
|
|
|
|
-- except (OSError, ValueError):
|
|
|
|
-- raise DMError("Error pulling remote file '%s' to '%s'" % (remoteFile, localFile))
|
|
|
|
--
|
|
|
|
-- def pullFile(self, remoteFile, offset=None, length=None):
|
|
|
|
-- # TODO: add debug flags and allow for printing stdout
|
|
|
|
-- with mozfile.NamedTemporaryFile() as tf:
|
|
|
|
-- self._runPull(remoteFile, tf.name)
|
|
|
|
-- # we need to reopen the file to get the written contents
|
|
|
|
-- with open(tf.name) as tf2:
|
|
|
|
-- # ADB pull does not support offset and length, but we can
|
|
|
|
-- # instead read only the requested portion of the local file
|
|
|
|
-- if offset is not None and length is not None:
|
|
|
|
-- tf2.seek(offset)
|
|
|
|
-- return tf2.read(length)
|
|
|
|
-- elif offset is not None:
|
|
|
|
-- tf2.seek(offset)
|
|
|
|
-- return tf2.read()
|
|
|
|
-- else:
|
|
|
|
-- return tf2.read()
|
|
|
|
--
|
|
|
|
-- def getFile(self, remoteFile, localFile):
|
|
|
|
-- self._runPull(remoteFile, localFile)
|
|
|
|
--
|
|
|
|
-- def getDirectory(self, remoteDir, localDir, checkDir=True):
|
|
|
|
-- localDir = os.path.normpath(localDir)
|
|
|
|
-- remoteDir = os.path.normpath(remoteDir)
|
|
|
|
-- copyRequired = False
|
|
|
|
-- originalLocal = localDir
|
|
|
|
-- if self._adb_version >= '1.0.36' and \
|
|
|
|
-- os.path.isdir(localDir) and self.dirExists(remoteDir):
|
|
|
|
-- # See do_sync_pull in
|
|
|
|
-- # https://android.googlesource.com/platform/system/core/+/master/adb/file_sync_client.cpp
|
|
|
|
-- # Work around change in behavior in adb 1.0.36 where if
|
|
|
|
-- # the local destination directory exists, adb pull will
|
|
|
|
-- # copy the source directory *into* the destination
|
|
|
|
-- # directory otherwise it will copy the source directory
|
|
|
|
-- # *onto* the destination directory.
|
|
|
|
-- #
|
|
|
|
-- # If the destination directory does exist, pull to its
|
|
|
|
-- # parent directory. If the source and destination leaf
|
|
|
|
-- # directory names are different, pull the source directory
|
|
|
|
-- # into a temporary directory and then copy the temporary
|
|
|
|
-- # directory onto the destination.
|
|
|
|
-- localName = os.path.basename(localDir)
|
|
|
|
-- remoteName = os.path.basename(remoteDir)
|
|
|
|
-- if localName != remoteName:
|
|
|
|
-- copyRequired = True
|
|
|
|
-- tempParent = tempfile.mkdtemp()
|
|
|
|
-- localDir = os.path.join(tempParent, remoteName)
|
|
|
|
-- else:
|
|
|
|
-- localDir = '/'.join(localDir.rstrip('/').split('/')[:-1])
|
|
|
|
-- self._runCmd(["pull", remoteDir, localDir]).wait()
|
|
|
|
-- if copyRequired:
|
|
|
|
-- dir_util.copy_tree(localDir, originalLocal)
|
|
|
|
-- mozfile.remove(tempParent)
|
|
|
|
--
|
|
|
|
-- def validateFile(self, remoteFile, localFile):
|
|
|
|
-- md5Remote = self._getRemoteHash(remoteFile)
|
|
|
|
-- md5Local = self._getLocalHash(localFile)
|
|
|
|
-- if md5Remote is None or md5Local is None:
|
|
|
|
-- return None
|
|
|
|
-- return md5Remote == md5Local
|
|
|
|
--
|
|
|
|
-- def _getRemoteHash(self, remoteFile):
|
|
|
|
-- """
|
|
|
|
-- Return the md5 sum of a file on the device
|
|
|
|
-- """
|
|
|
|
-- with tempfile.NamedTemporaryFile() as f:
|
|
|
|
-- self._runPull(remoteFile, f.name)
|
|
|
|
--
|
|
|
|
-- return self._getLocalHash(f.name)
|
|
|
|
--
|
|
|
|
-- def _setupDeviceRoot(self, deviceRoot):
|
|
|
|
-- # user-specified device root, create it and return it
|
|
|
|
-- if deviceRoot:
|
|
|
|
-- self.mkDir(deviceRoot)
|
|
|
|
-- return deviceRoot
|
|
|
|
--
|
|
|
|
-- # we must determine the device root ourselves
|
|
|
|
-- paths = [('/storage/sdcard0', 'tests'),
|
|
|
|
-- ('/storage/sdcard1', 'tests'),
|
|
|
|
-- ('/storage/sdcard', 'tests'),
|
|
|
|
-- ('/mnt/sdcard', 'tests'),
|
|
|
|
-- ('/sdcard', 'tests'),
|
|
|
|
-- ('/data/local', 'tests')]
|
|
|
|
-- for (basePath, subPath) in paths:
|
|
|
|
-- if self.dirExists(basePath):
|
|
|
|
-- root = os.path.join(basePath, subPath)
|
|
|
|
-- try:
|
|
|
|
-- self.mkDir(root)
|
|
|
|
-- return root
|
|
|
|
-- except Exception:
|
|
|
|
-- pass
|
|
|
|
--
|
|
|
|
-- raise DMError("Unable to set up device root using paths: [%s]"
|
|
|
|
-- % ", ".join(["'%s'" % os.path.join(b, s) for b, s in paths]))
|
|
|
|
--
|
|
|
|
-- def getTempDir(self):
|
|
|
|
-- # Cache result to speed up operations depending
|
|
|
|
-- # on the temporary directory.
|
|
|
|
-- if not self._tempDir:
|
|
|
|
-- self._tempDir = "%s/tmp" % self.deviceRoot
|
|
|
|
-- self.mkDir(self._tempDir)
|
|
|
|
--
|
|
|
|
-- return self._tempDir
|
|
|
|
--
|
|
|
|
-- def reboot(self, wait=False, **kwargs):
|
|
|
|
-- self._checkCmd(["reboot"])
|
|
|
|
-- if wait:
|
|
|
|
-- self._checkCmd(["wait-for-device"])
|
|
|
|
-- if self._runAdbAsRoot:
|
|
|
|
-- self._adb_root()
|
|
|
|
-- self._checkCmd(["shell", "ls", "/sbin"], timeout=self.short_timeout)
|
|
|
|
--
|
|
|
|
-- def updateApp(self, appBundlePath, **kwargs):
|
|
|
|
-- return self._runCmd(["install", "-r", appBundlePath]).output
|
|
|
|
--
|
|
|
|
-- def getCurrentTime(self):
|
|
|
|
-- timestr = str(self._runCmd(["shell", "date", "+%s"], timeout=self.short_timeout).output[0])
|
|
|
|
-- if (not timestr or not timestr.isdigit()):
|
|
|
|
-- raise DMError("Unable to get current time using date (got: '%s')" % timestr)
|
|
|
|
-- return int(timestr) * 1000
|
|
|
|
--
|
|
|
|
-- def getInfo(self, directive=None):
|
|
|
|
-- directive = directive or "all"
|
|
|
|
-- ret = {}
|
|
|
|
-- if directive == "id" or directive == "all":
|
|
|
|
-- ret["id"] = self._runCmd(["get-serialno"], timeout=self.short_timeout).output[0]
|
|
|
|
-- if directive == "os" or directive == "all":
|
|
|
|
-- ret["os"] = self.shellCheckOutput(
|
|
|
|
-- ["getprop", "ro.build.display.id"], timeout=self.short_timeout)
|
|
|
|
-- if directive == "uptime" or directive == "all":
|
|
|
|
-- uptime = self.shellCheckOutput(["uptime"], timeout=self.short_timeout)
|
|
|
|
-- if not uptime:
|
|
|
|
-- raise DMError("error getting uptime")
|
|
|
|
-- m = re.match("up time: ((\d+) days, )*(\d{2}):(\d{2}):(\d{2})", uptime)
|
|
|
|
-- if m:
|
|
|
|
-- uptime = "%d days %d hours %d minutes %d seconds" % tuple(
|
|
|
|
-- [int(g or 0) for g in m.groups()[1:]])
|
|
|
|
-- ret["uptime"] = uptime
|
|
|
|
-- if directive == "process" or directive == "all":
|
|
|
|
-- data = self.shellCheckOutput(["ps"], timeout=self.short_timeout)
|
|
|
|
-- ret["process"] = data.split('\n')
|
|
|
|
-- if directive == "systime" or directive == "all":
|
|
|
|
-- ret["systime"] = self.shellCheckOutput(["date"], timeout=self.short_timeout)
|
|
|
|
-- if directive == "memtotal" or directive == "all":
|
|
|
|
-- meminfo = {}
|
|
|
|
-- for line in self.pullFile("/proc/meminfo").splitlines():
|
|
|
|
-- key, value = line.split(":")
|
|
|
|
-- meminfo[key] = value.strip()
|
|
|
|
-- ret["memtotal"] = meminfo["MemTotal"]
|
|
|
|
-- if directive == "disk" or directive == "all":
|
|
|
|
-- data = self.shellCheckOutput(
|
|
|
|
-- ["df", "/data", "/system", "/sdcard"], timeout=self.short_timeout)
|
|
|
|
-- ret["disk"] = data.split('\n')
|
|
|
|
-- self._logger.debug("getInfo: %s" % ret)
|
|
|
|
-- return ret
|
|
|
|
--
|
|
|
|
-- def uninstallApp(self, appName, installPath=None):
|
|
|
|
-- status = self._runCmd(["uninstall", appName]).output[0].strip()
|
|
|
|
-- if status != 'Success':
|
|
|
|
-- raise DMError("uninstall failed for %s. Got: %s" % (appName, status))
|
|
|
|
--
|
|
|
|
-- def uninstallAppAndReboot(self, appName, installPath=None):
|
|
|
|
-- self.uninstallApp(appName)
|
|
|
|
-- self.reboot()
|
|
|
|
--
|
|
|
|
-- def _runCmd(self, args, timeout=None, retryLimit=None):
|
|
|
|
-- """
|
|
|
|
-- Runs a command using adb
|
|
|
|
-- If timeout is specified, the process is killed after <timeout> seconds.
|
|
|
|
--
|
|
|
|
-- returns: instance of ProcessHandler
|
|
|
|
-- """
|
|
|
|
-- retryLimit = retryLimit or self.retryLimit
|
|
|
|
-- finalArgs = [self._adbPath]
|
|
|
|
-- if self._serverHost is not None:
|
|
|
|
-- finalArgs.extend(['-H', self._serverHost])
|
|
|
|
-- if self._serverPort is not None:
|
|
|
|
-- finalArgs.extend(['-P', str(self._serverPort)])
|
|
|
|
-- if self._deviceSerial:
|
|
|
|
-- finalArgs.extend(['-s', self._deviceSerial])
|
|
|
|
-- finalArgs.extend(args)
|
|
|
|
-- self._logger.debug("_runCmd - command: %s" % ' '.join(finalArgs))
|
|
|
|
-- if not timeout:
|
|
|
|
-- timeout = self.default_timeout
|
|
|
|
--
|
|
|
|
-- def _timeout():
|
|
|
|
-- self._logger.error("Timeout exceeded for _runCmd call '%s'" % ' '.join(finalArgs))
|
|
|
|
--
|
|
|
|
-- retries = 0
|
|
|
|
-- while retries < retryLimit:
|
|
|
|
-- proc = ProcessHandler(finalArgs, storeOutput=True,
|
|
|
|
-- processOutputLine=self._log, onTimeout=_timeout)
|
|
|
|
-- proc.run(timeout=timeout)
|
|
|
|
-- proc.returncode = proc.wait()
|
|
|
|
-- if proc.returncode is None:
|
|
|
|
-- proc.kill()
|
|
|
|
-- retries += 1
|
|
|
|
-- else:
|
|
|
|
-- return proc
|
|
|
|
--
|
|
|
|
-- # timeout is specified in seconds, and if no timeout is given,
|
|
|
|
-- # we will run until we hit the default_timeout specified in the __init__
|
|
|
|
-- def _checkCmd(self, args, timeout=None, retryLimit=None):
|
|
|
|
-- """
|
|
|
|
-- Runs a command using adb and waits for the command to finish.
|
|
|
|
-- If timeout is specified, the process is killed after <timeout> seconds.
|
|
|
|
--
|
|
|
|
-- returns: returncode from process
|
|
|
|
-- """
|
|
|
|
-- retryLimit = retryLimit or self.retryLimit
|
|
|
|
-- finalArgs = [self._adbPath]
|
|
|
|
-- if self._serverHost is not None:
|
|
|
|
-- finalArgs.extend(['-H', self._serverHost])
|
|
|
|
-- if self._serverPort is not None:
|
|
|
|
-- finalArgs.extend(['-P', str(self._serverPort)])
|
|
|
|
-- if self._deviceSerial:
|
|
|
|
-- finalArgs.extend(['-s', self._deviceSerial])
|
|
|
|
-- finalArgs.extend(args)
|
|
|
|
-- self._logger.debug("_checkCmd - command: %s" % ' '.join(finalArgs))
|
|
|
|
-- if not timeout:
|
|
|
|
-- # We are asserting that all commands will complete in this
|
|
|
|
-- # time unless otherwise specified
|
|
|
|
-- timeout = self.default_timeout
|
|
|
|
--
|
|
|
|
-- def _timeout():
|
|
|
|
-- self._logger.error("Timeout exceeded for _checkCmd call '%s'" % ' '.join(finalArgs))
|
|
|
|
--
|
|
|
|
-- timeout = int(timeout)
|
|
|
|
-- retries = 0
|
|
|
|
-- while retries < retryLimit:
|
|
|
|
-- proc = ProcessHandler(finalArgs, processOutputLine=self._log, onTimeout=_timeout)
|
|
|
|
-- proc.run(timeout=timeout)
|
|
|
|
-- ret_code = proc.wait()
|
|
|
|
-- if ret_code is None:
|
|
|
|
-- proc.kill()
|
|
|
|
-- retries += 1
|
|
|
|
-- else:
|
|
|
|
-- return ret_code
|
|
|
|
--
|
|
|
|
-- raise DMError("Timeout exceeded for _checkCmd call after %d retries." % retries)
|
|
|
|
--
|
|
|
|
-- def chmodDir(self, remoteDir, mask="777"):
|
|
|
|
-- if (self.dirExists(remoteDir)):
|
|
|
|
-- if '/sdcard' in remoteDir:
|
|
|
|
-- self._logger.debug("chmod %s -- skipped (/sdcard)" % remoteDir)
|
|
|
|
-- else:
|
|
|
|
-- files = self.listFiles(remoteDir.strip())
|
|
|
|
-- for f in files:
|
|
|
|
-- remoteEntry = remoteDir.strip() + "/" + f.strip()
|
|
|
|
-- if (self.dirExists(remoteEntry)):
|
|
|
|
-- self.chmodDir(remoteEntry)
|
|
|
|
-- else:
|
|
|
|
-- self._checkCmd(["shell", "chmod", mask, remoteEntry],
|
|
|
|
-- timeout=self.short_timeout)
|
|
|
|
-- self._logger.info("chmod %s" % remoteEntry)
|
|
|
|
-- self._checkCmd(["shell", "chmod", mask, remoteDir], timeout=self.short_timeout)
|
|
|
|
-- self._logger.debug("chmod %s" % remoteDir)
|
|
|
|
-- else:
|
|
|
|
-- self._checkCmd(["shell", "chmod", mask, remoteDir.strip()], timeout=self.short_timeout)
|
|
|
|
-- self._logger.debug("chmod %s" % remoteDir.strip())
|
|
|
|
--
|
|
|
|
-- def _verifyADB(self):
|
|
|
|
-- """
|
|
|
|
-- Check to see if adb itself can be executed.
|
|
|
|
-- """
|
|
|
|
-- if self._adbPath != 'adb':
|
|
|
|
-- if not os.access(self._adbPath, os.X_OK):
|
|
|
|
-- raise DMError("invalid adb path, or adb not executable: %s" % self._adbPath)
|
|
|
|
--
|
|
|
|
-- try:
|
|
|
|
-- re_version = re.compile(r'Android Debug Bridge version (.*)')
|
|
|
|
-- proc = self._runCmd(["version"], timeout=self.short_timeout)
|
|
|
|
-- self._adb_version = re_version.match(proc.output[0]).group(1)
|
|
|
|
-- self._logger.info("Detected adb %s" % self._adb_version)
|
|
|
|
-- except os.error as err:
|
|
|
|
-- raise DMError(
|
|
|
|
-- "unable to execute ADB (%s): ensure Android SDK is installed "
|
|
|
|
-- "and adb is in your $PATH" % err)
|
|
|
|
--
|
|
|
|
-- def _verifyDevice(self):
|
|
|
|
-- # If there is a device serial number, see if adb is connected to it
|
|
|
|
-- if self._deviceSerial:
|
|
|
|
-- deviceStatus = None
|
|
|
|
-- for line in self._runCmd(["devices"]).output:
|
|
|
|
-- m = re.match('(.+)?\s+(.+)$', line)
|
|
|
|
-- if m:
|
|
|
|
-- if self._deviceSerial == m.group(1):
|
|
|
|
-- deviceStatus = m.group(2)
|
|
|
|
-- if deviceStatus is None:
|
|
|
|
-- raise DMError("device not found: %s" % self._deviceSerial)
|
|
|
|
-- elif deviceStatus != "device":
|
|
|
|
-- raise DMError("bad status for device %s: %s" % (self._deviceSerial, deviceStatus))
|
|
|
|
--
|
|
|
|
-- # Check to see if we can connect to device and run a simple command
|
|
|
|
-- if not self._checkCmd(["shell", "echo"], timeout=self.short_timeout) == 0:
|
|
|
|
-- raise DMError("unable to connect to device")
|
|
|
|
--
|
|
|
|
-- def _checkForRoot(self):
|
|
|
|
-- self._haveRootShell = False
|
|
|
|
-- self._haveSu = False
|
|
|
|
-- # If requested to attempt to run adbd as root, do so before
|
|
|
|
-- # checking whether adbs is running as root.
|
|
|
|
-- if self._runAdbAsRoot:
|
|
|
|
-- self._adb_root()
|
|
|
|
--
|
|
|
|
-- # Check whether we _are_ root by default (some development boards work
|
|
|
|
-- # this way, this is also the result of some relatively rare rooting
|
|
|
|
-- # techniques)
|
|
|
|
-- proc = self._runCmd(["shell", "id"], timeout=self.short_timeout)
|
|
|
|
-- if proc.output and 'uid=0(root)' in proc.output[0]:
|
|
|
|
-- self._haveRootShell = True
|
|
|
|
-- # if this returns true, we don't care about su
|
|
|
|
-- return
|
|
|
|
--
|
|
|
|
-- # if root shell is not available, check if 'su' can be used to gain
|
|
|
|
-- # root
|
|
|
|
-- def su_id(su_modifier, timeout):
|
|
|
|
-- proc = self._runCmd(["shell", "su", su_modifier, "id"],
|
|
|
|
-- timeout=timeout)
|
|
|
|
--
|
|
|
|
-- # wait for response for maximum of 15 seconds, in case su
|
|
|
|
-- # prompts for a password or triggers the Android SuperUser
|
|
|
|
-- # prompt
|
|
|
|
-- start_time = time.time()
|
|
|
|
-- retcode = None
|
|
|
|
-- while (time.time() - start_time) <= 15 and retcode is None:
|
|
|
|
-- retcode = proc.poll()
|
|
|
|
-- if retcode is None: # still not terminated, kill
|
|
|
|
-- proc.kill()
|
|
|
|
--
|
|
|
|
-- if proc.output and 'uid=0(root)' in proc.output[0]:
|
|
|
|
-- return True
|
|
|
|
-- return False
|
|
|
|
--
|
|
|
|
-- if su_id('0', self.short_timeout):
|
|
|
|
-- self._haveSu = True
|
|
|
|
-- self._suModifier = '0'
|
|
|
|
-- elif su_id('-c', self.short_timeout):
|
|
|
|
-- self._haveSu = True
|
|
|
|
-- self._suModifier = '-c'
|
|
|
|
--
|
|
|
|
-- def _isUnzipAvailable(self):
|
|
|
|
-- data = self._runCmd(["shell", "unzip"]).output
|
|
|
|
-- for line in data:
|
|
|
|
-- if (re.search('Usage', line)):
|
|
|
|
-- return True
|
|
|
|
-- return False
|
|
|
|
--
|
|
|
|
-- def _isLocalZipAvailable(self):
|
|
|
|
-- def _noOutput(line):
|
|
|
|
-- # suppress output from zip ProcessHandler
|
|
|
|
-- pass
|
|
|
|
-- try:
|
|
|
|
-- proc = ProcessHandler(["zip", "-?"], storeOutput=False, processOutputLine=_noOutput)
|
|
|
|
-- proc.run()
|
|
|
|
-- proc.wait()
|
|
|
|
-- except Exception:
|
|
|
|
-- return False
|
|
|
|
-- return True
|
|
|
|
--
|
|
|
|
-- def _verifyZip(self):
|
|
|
|
-- # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
|
|
|
|
-- # can use these to push just one file per directory -- a significant
|
|
|
|
-- # optimization for large directories.
|
|
|
|
-- self._useZip = False
|
|
|
|
-- if (self._isUnzipAvailable() and self._isLocalZipAvailable()):
|
|
|
|
-- self._logger.info("will use zip to push directories")
|
|
|
|
-- self._useZip = True
|
|
|
|
-- else:
|
|
|
|
-- raise DMError("zip not available")
|
|
|
|
--
|
|
|
|
-- def _adb_root(self):
|
|
|
|
-- """ Some devices require us to reboot adbd as root.
|
|
|
|
-- This function takes care of it.
|
|
|
|
-- """
|
|
|
|
-- if self.processInfo("adbd")[2] != "root":
|
|
|
|
-- self._checkCmd(["root"])
|
|
|
|
-- self._checkCmd(["wait-for-device"])
|
|
|
|
-- if self.processInfo("adbd")[2] != "root":
|
|
|
|
-- raise DMError("We tried rebooting adbd as root, however, it failed.")
|
|
|
|
--
|
|
|
|
-- def _detectLsModifier(self):
|
|
|
|
-- if self._lsModifier is None:
|
|
|
|
-- # Check if busybox -1A is required in order to get one
|
|
|
|
-- # file per line.
|
|
|
|
-- output = self._runCmd(["shell", "ls", "-1A", "/"],
|
|
|
|
-- timeout=self.short_timeout).output
|
|
|
|
-- output = ' '.join(output)
|
|
|
|
-- if 'error: device not found' in output:
|
|
|
|
-- raise DMError(output)
|
|
|
|
-- if "Unknown option '-1'. Aborting." in output:
|
|
|
|
-- self._lsModifier = "-a"
|
|
|
|
-- elif "No such file or directory" in output:
|
|
|
|
-- self._lsModifier = "-a"
|
|
|
|
-- else:
|
|
|
|
-- self._lsModifier = "-1A"
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/mozdevice/dmcli.py b/testing/mozbase/mozdevice/mozdevice/dmcli.py
|
|
|
|
-deleted file mode 100644
|
|
|
|
---- a/testing/mozbase/mozdevice/mozdevice/dmcli.py
|
|
|
|
-+++ /dev/null
|
|
|
|
-@@ -1,352 +0,0 @@
|
|
|
|
--# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
--# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
--# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
--
|
|
|
|
--"""
|
|
|
|
--Command-line client to control a device
|
|
|
|
--"""
|
|
|
|
--from __future__ import absolute_import, print_function
|
|
|
|
--
|
|
|
|
--import errno
|
|
|
|
--import logging
|
|
|
|
--import os
|
|
|
|
--import posixpath
|
|
|
|
--import StringIO
|
|
|
|
--import sys
|
|
|
|
--import mozdevice
|
|
|
|
--import mozlog
|
|
|
|
--import argparse
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class DMCli(object):
|
|
|
|
--
|
|
|
|
-- def __init__(self):
|
|
|
|
-- self.commands = {'deviceroot': {'function': self.deviceroot,
|
|
|
|
-- 'help': 'get device root directory for storing temporary '
|
|
|
|
-- 'files'},
|
|
|
|
-- 'install': {'function': self.install,
|
|
|
|
-- 'args': [{'name': 'file'}],
|
|
|
|
-- 'help': 'push this package file to the device'
|
|
|
|
-- ' and install it'},
|
|
|
|
-- 'uninstall': {'function': self.uninstall,
|
|
|
|
-- 'args': [{'name': 'packagename'}],
|
|
|
|
-- 'help': 'uninstall the named app from the device'},
|
|
|
|
-- 'killapp': {'function': self.kill,
|
|
|
|
-- 'args': [{'name': 'process_name', 'nargs': '*'}],
|
|
|
|
-- 'help': 'kills any processes with name(s) on device'},
|
|
|
|
-- 'launchapp': {'function': self.launchapp,
|
|
|
|
-- 'args': [{'name': 'appname'},
|
|
|
|
-- {'name': 'activity_name'},
|
|
|
|
-- {'name': '--intent',
|
|
|
|
-- 'action': 'store',
|
|
|
|
-- 'default': 'android.intent.action.VIEW'},
|
|
|
|
-- {'name': '--url',
|
|
|
|
-- 'action': 'store'},
|
|
|
|
-- {'name': '--no-fail-if-running',
|
|
|
|
-- 'action': 'store_true',
|
|
|
|
-- 'help': 'Don\'t fail if application is'
|
|
|
|
-- ' already running'}
|
|
|
|
-- ],
|
|
|
|
-- 'help': 'launches application on device'},
|
|
|
|
-- 'listapps': {'function': self.listapps,
|
|
|
|
-- 'help': 'list applications on device'},
|
|
|
|
-- 'push': {'function': self.push,
|
|
|
|
-- 'args': [{'name': 'local_file'},
|
|
|
|
-- {'name': 'remote_file'}
|
|
|
|
-- ],
|
|
|
|
-- 'help': 'copy file/dir to device'},
|
|
|
|
-- 'pull': {'function': self.pull,
|
|
|
|
-- 'args': [{'name': 'local_file'},
|
|
|
|
-- {'name': 'remote_file', 'nargs': '?'}],
|
|
|
|
-- 'help': 'copy file/dir from device'},
|
|
|
|
-- 'shell': {'function': self.shell,
|
|
|
|
-- 'args': [{'name': 'command', 'nargs': argparse.REMAINDER},
|
|
|
|
-- {'name': '--root', 'action': 'store_true',
|
|
|
|
-- 'help': 'Run command as root'}],
|
|
|
|
-- 'help': 'run shell command on device'},
|
|
|
|
-- 'info': {'function': self.getinfo,
|
|
|
|
-- 'args': [{'name': 'directive', 'nargs': '?'}],
|
|
|
|
-- 'help': 'get information on specified '
|
|
|
|
-- 'aspect of the device (if no argument '
|
|
|
|
-- 'given, print all available information)'
|
|
|
|
-- },
|
|
|
|
-- 'ps': {'function': self.processlist,
|
|
|
|
-- 'help': 'get information on running processes on device'
|
|
|
|
-- },
|
|
|
|
-- 'logcat': {'function': self.logcat,
|
|
|
|
-- 'help': 'get logcat from device'
|
|
|
|
-- },
|
|
|
|
-- 'ls': {'function': self.listfiles,
|
|
|
|
-- 'args': [{'name': 'remote_dir'}],
|
|
|
|
-- 'help': 'list files on device'
|
|
|
|
-- },
|
|
|
|
-- 'rm': {'function': self.removefile,
|
|
|
|
-- 'args': [{'name': 'remote_file'}],
|
|
|
|
-- 'help': 'remove file from device'
|
|
|
|
-- },
|
|
|
|
-- 'isdir': {'function': self.isdir,
|
|
|
|
-- 'args': [{'name': 'remote_dir'}],
|
|
|
|
-- 'help': 'print if remote file is a directory'
|
|
|
|
-- },
|
|
|
|
-- 'mkdir': {'function': self.mkdir,
|
|
|
|
-- 'args': [{'name': 'remote_dir'}],
|
|
|
|
-- 'help': 'makes a directory on device'
|
|
|
|
-- },
|
|
|
|
-- 'rmdir': {'function': self.rmdir,
|
|
|
|
-- 'args': [{'name': 'remote_dir'}],
|
|
|
|
-- 'help': 'recursively remove directory from device'
|
|
|
|
-- },
|
|
|
|
-- 'screencap': {'function': self.screencap,
|
|
|
|
-- 'args': [{'name': 'png_file'}],
|
|
|
|
-- 'help': 'capture screenshot of device in action'
|
|
|
|
-- },
|
|
|
|
-- 'clearlogcat': {'function': self.clearlogcat,
|
|
|
|
-- 'help': 'clear the logcat'
|
|
|
|
-- },
|
|
|
|
-- 'reboot': {'function': self.reboot,
|
|
|
|
-- 'help': 'reboot the device',
|
|
|
|
-- 'args': [{'name': '--wait',
|
|
|
|
-- 'action': 'store_true',
|
|
|
|
-- 'help': 'Wait for device to come back up'
|
|
|
|
-- ' before exiting'}]
|
|
|
|
--
|
|
|
|
-- },
|
|
|
|
-- 'isfile': {'function': self.isfile,
|
|
|
|
-- 'args': [{'name': 'remote_file'}],
|
|
|
|
-- 'help': 'check whether a file exists on the device'
|
|
|
|
-- },
|
|
|
|
-- 'launchfennec': {'function': self.launchfennec,
|
|
|
|
-- 'args': [{'name': 'appname'},
|
|
|
|
-- {'name': '--intent', 'action': 'store',
|
|
|
|
-- 'default': 'android.intent.action.VIEW'},
|
|
|
|
-- {'name': '--url', 'action': 'store'},
|
|
|
|
-- {'name': '--extra-args', 'action': 'store'},
|
|
|
|
-- {'name': '--mozenv', 'action': 'store',
|
|
|
|
-- 'help': 'Gecko environment variables to set'
|
|
|
|
-- ' in "KEY1=VAL1 KEY2=VAL2" format'},
|
|
|
|
-- {'name': '--no-fail-if-running',
|
|
|
|
-- 'action': 'store_true',
|
|
|
|
-- 'help': 'Don\'t fail if application is '
|
|
|
|
-- 'already running'}
|
|
|
|
-- ],
|
|
|
|
-- 'help': 'launch fennec'
|
|
|
|
-- },
|
|
|
|
-- 'getip': {'function': self.getip,
|
|
|
|
-- 'args': [{'name': 'interface', 'nargs': '*'}],
|
|
|
|
-- 'help': 'get the ip address of the device'
|
|
|
|
-- }
|
|
|
|
-- }
|
|
|
|
--
|
|
|
|
-- self.parser = argparse.ArgumentParser()
|
|
|
|
-- self.add_options(self.parser)
|
|
|
|
-- self.add_commands(self.parser)
|
|
|
|
-- mozlog.commandline.add_logging_group(self.parser)
|
|
|
|
--
|
|
|
|
-- def run(self, args=sys.argv[1:]):
|
|
|
|
-- args = self.parser.parse_args()
|
|
|
|
--
|
|
|
|
-- mozlog.commandline.setup_logging(
|
|
|
|
-- 'mozdevice', args, {'mach': sys.stdout})
|
|
|
|
--
|
|
|
|
-- self.dm = self.getDevice(hwid=args.hwid,
|
|
|
|
-- host=args.host, port=args.port,
|
|
|
|
-- verbose=args.verbose)
|
|
|
|
--
|
|
|
|
-- ret = args.func(args)
|
|
|
|
-- if ret is None:
|
|
|
|
-- ret = 0
|
|
|
|
--
|
|
|
|
-- sys.exit(ret)
|
|
|
|
--
|
|
|
|
-- def add_options(self, parser):
|
|
|
|
-- parser.add_argument("-v", "--verbose", action="store_true",
|
|
|
|
-- help="Verbose output from DeviceManager",
|
|
|
|
-- default=bool(os.environ.get('VERBOSE')))
|
|
|
|
-- parser.add_argument("--host", action="store",
|
|
|
|
-- help="Device hostname (only if using TCP/IP, "
|
|
|
|
-- "defaults to TEST_DEVICE environment "
|
|
|
|
-- "variable if present)",
|
|
|
|
-- default=os.environ.get('TEST_DEVICE'))
|
|
|
|
-- parser.add_argument("-p", "--port", action="store",
|
|
|
|
-- type=int,
|
|
|
|
-- help="Custom device port (if using "
|
|
|
|
-- "adb-over-tcp)", default=None)
|
|
|
|
-- parser.add_argument("-d", "--hwid", action="store",
|
|
|
|
-- help="HWID", default=None)
|
|
|
|
-- parser.add_argument("--package-name", action="store",
|
|
|
|
-- help="Packagename (if using DeviceManagerADB)",
|
|
|
|
-- default=None)
|
|
|
|
--
|
|
|
|
-- def add_commands(self, parser):
|
|
|
|
-- subparsers = parser.add_subparsers(title="Commands", metavar="<command>")
|
|
|
|
-- for (commandname, commandprops) in sorted(self.commands.iteritems()):
|
|
|
|
-- subparser = subparsers.add_parser(commandname, help=commandprops['help'])
|
|
|
|
-- if commandprops.get('args'):
|
|
|
|
-- for arg in commandprops['args']:
|
|
|
|
-- # this is more elegant but doesn't work in python 2.6
|
|
|
|
-- # (which we still use on tbpl @ mozilla where we install
|
|
|
|
-- # this package)
|
|
|
|
-- # kwargs = { k: v for k,v in arg.items() if k is not 'name' }
|
|
|
|
-- kwargs = {}
|
|
|
|
-- for (k, v) in arg.items():
|
|
|
|
-- if k is not 'name':
|
|
|
|
-- kwargs[k] = v
|
|
|
|
-- subparser.add_argument(arg['name'], **kwargs)
|
|
|
|
-- subparser.set_defaults(func=commandprops['function'])
|
|
|
|
--
|
|
|
|
-- def getDevice(self, hwid=None, host=None, port=None,
|
|
|
|
-- packagename=None, verbose=False):
|
|
|
|
-- '''
|
|
|
|
-- Returns a device with the specified parameters
|
|
|
|
-- '''
|
|
|
|
-- logLevel = logging.ERROR
|
|
|
|
-- if verbose:
|
|
|
|
-- logLevel = logging.DEBUG
|
|
|
|
--
|
|
|
|
-- if host and not port:
|
|
|
|
-- port = 5555
|
|
|
|
-- return mozdevice.DroidADB(packageName=packagename,
|
|
|
|
-- host=host, port=port,
|
|
|
|
-- logLevel=logLevel)
|
|
|
|
--
|
|
|
|
-- def deviceroot(self, args):
|
|
|
|
-- print(self.dm.deviceRoot)
|
|
|
|
--
|
|
|
|
-- def push(self, args):
|
|
|
|
-- (src, dest) = (args.local_file, args.remote_file)
|
|
|
|
-- if os.path.isdir(src):
|
|
|
|
-- self.dm.pushDir(src, dest)
|
|
|
|
-- else:
|
|
|
|
-- dest_is_dir = dest[-1] == '/' or self.dm.dirExists(dest)
|
|
|
|
-- dest = posixpath.normpath(dest)
|
|
|
|
-- if dest_is_dir:
|
|
|
|
-- dest = posixpath.join(dest, os.path.basename(src))
|
|
|
|
-- self.dm.pushFile(src, dest)
|
|
|
|
--
|
|
|
|
-- def pull(self, args):
|
|
|
|
-- (src, dest) = (args.local_file, args.remote_file)
|
|
|
|
-- if not self.dm.fileExists(src):
|
|
|
|
-- print('No such file or directory')
|
|
|
|
-- return
|
|
|
|
-- if not dest:
|
|
|
|
-- dest = posixpath.basename(src)
|
|
|
|
-- if self.dm.dirExists(src):
|
|
|
|
-- self.dm.getDirectory(src, dest)
|
|
|
|
-- else:
|
|
|
|
-- self.dm.getFile(src, dest)
|
|
|
|
--
|
|
|
|
-- def install(self, args):
|
|
|
|
-- basename = os.path.basename(args.file)
|
|
|
|
-- app_path_on_device = posixpath.join(self.dm.deviceRoot,
|
|
|
|
-- basename)
|
|
|
|
-- self.dm.pushFile(args.file, app_path_on_device)
|
|
|
|
-- self.dm.installApp(app_path_on_device)
|
|
|
|
--
|
|
|
|
-- def uninstall(self, args):
|
|
|
|
-- self.dm.uninstallApp(args.packagename)
|
|
|
|
--
|
|
|
|
-- def launchapp(self, args):
|
|
|
|
-- self.dm.launchApplication(args.appname, args.activity_name,
|
|
|
|
-- args.intent, url=args.url,
|
|
|
|
-- failIfRunning=(not args.no_fail_if_running))
|
|
|
|
--
|
|
|
|
-- def listapps(self, args):
|
|
|
|
-- for app in self.dm.getInstalledApps():
|
|
|
|
-- print(app)
|
|
|
|
--
|
|
|
|
-- def stopapp(self, args):
|
|
|
|
-- self.dm.stopApplication(args.appname)
|
|
|
|
--
|
|
|
|
-- def kill(self, args):
|
|
|
|
-- for name in args.process_name:
|
|
|
|
-- self.dm.killProcess(name)
|
|
|
|
--
|
|
|
|
-- def shell(self, args):
|
|
|
|
-- buf = StringIO.StringIO()
|
|
|
|
-- self.dm.shell(args.command, buf, root=args.root)
|
|
|
|
-- print(str(buf.getvalue()[0:-1]).rstrip())
|
|
|
|
--
|
|
|
|
-- def getinfo(self, args):
|
|
|
|
-- info = self.dm.getInfo(directive=args.directive)
|
|
|
|
-- for (infokey, infoitem) in sorted(info.iteritems()):
|
|
|
|
-- if infokey == "process":
|
|
|
|
-- pass # skip process list: get that through ps
|
|
|
|
-- elif args.directive is None:
|
|
|
|
-- print("%s: %s" % (infokey.upper(), infoitem))
|
|
|
|
-- else:
|
|
|
|
-- print(infoitem)
|
|
|
|
--
|
|
|
|
-- def logcat(self, args):
|
|
|
|
-- print(''.join(self.dm.getLogcat()))
|
|
|
|
--
|
|
|
|
-- def clearlogcat(self, args):
|
|
|
|
-- self.dm.recordLogcat()
|
|
|
|
--
|
|
|
|
-- def reboot(self, args):
|
|
|
|
-- self.dm.reboot(wait=args.wait)
|
|
|
|
--
|
|
|
|
-- def processlist(self, args):
|
|
|
|
-- pslist = self.dm.getProcessList()
|
|
|
|
-- for ps in pslist:
|
|
|
|
-- print(" ".join(str(i) for i in ps))
|
|
|
|
--
|
|
|
|
-- def listfiles(self, args):
|
|
|
|
-- filelist = self.dm.listFiles(args.remote_dir)
|
|
|
|
-- for file in filelist:
|
|
|
|
-- print(file)
|
|
|
|
--
|
|
|
|
-- def removefile(self, args):
|
|
|
|
-- self.dm.removeFile(args.remote_file)
|
|
|
|
--
|
|
|
|
-- def isdir(self, args):
|
|
|
|
-- if self.dm.dirExists(args.remote_dir):
|
|
|
|
-- print("TRUE")
|
|
|
|
-- return
|
|
|
|
--
|
|
|
|
-- print("FALSE")
|
|
|
|
-- return errno.ENOTDIR
|
|
|
|
--
|
|
|
|
-- def mkdir(self, args):
|
|
|
|
-- self.dm.mkDir(args.remote_dir)
|
|
|
|
--
|
|
|
|
-- def rmdir(self, args):
|
|
|
|
-- self.dm.removeDir(args.remote_dir)
|
|
|
|
--
|
|
|
|
-- def screencap(self, args):
|
|
|
|
-- self.dm.saveScreenshot(args.png_file)
|
|
|
|
--
|
|
|
|
-- def isfile(self, args):
|
|
|
|
-- if self.dm.fileExists(args.remote_file):
|
|
|
|
-- print("TRUE")
|
|
|
|
-- return
|
|
|
|
-- print("FALSE")
|
|
|
|
-- return errno.ENOENT
|
|
|
|
--
|
|
|
|
-- def launchfennec(self, args):
|
|
|
|
-- mozEnv = None
|
|
|
|
-- if args.mozenv:
|
|
|
|
-- mozEnv = {}
|
|
|
|
-- keyvals = args.mozenv.split()
|
|
|
|
-- for keyval in keyvals:
|
|
|
|
-- (key, _, val) = keyval.partition("=")
|
|
|
|
-- mozEnv[key] = val
|
|
|
|
-- self.dm.launchFennec(args.appname, intent=args.intent,
|
|
|
|
-- mozEnv=mozEnv,
|
|
|
|
-- extraArgs=args.extra_args, url=args.url,
|
|
|
|
-- failIfRunning=(not args.no_fail_if_running))
|
|
|
|
--
|
|
|
|
-- def getip(self, args):
|
|
|
|
-- if args.interface:
|
|
|
|
-- print(self.dm.getIP(args.interface))
|
|
|
|
-- else:
|
|
|
|
-- print(self.dm.getIP())
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--def cli(args=sys.argv[1:]):
|
|
|
|
-- # process the command line
|
|
|
|
-- cli = DMCli()
|
|
|
|
-- cli.run(args)
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--if __name__ == '__main__':
|
|
|
|
-- cli()
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/mozdevice/droid.py b/testing/mozbase/mozdevice/mozdevice/droid.py
|
|
|
|
-deleted file mode 100644
|
|
|
|
---- a/testing/mozbase/mozdevice/mozdevice/droid.py
|
|
|
|
-+++ /dev/null
|
|
|
|
-@@ -1,198 +0,0 @@
|
|
|
|
--# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
--# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
--# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
--
|
|
|
|
--from __future__ import absolute_import
|
|
|
|
--
|
|
|
|
--import StringIO
|
|
|
|
--import re
|
|
|
|
--import time
|
|
|
|
--
|
|
|
|
--from mozdevice import version_codes
|
|
|
|
--
|
|
|
|
--from .devicemanagerADB import DeviceManagerADB
|
|
|
|
--from .devicemanager import DMError
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class DroidMixin(object):
|
|
|
|
-- """Mixin to extend DeviceManager with Android-specific functionality"""
|
|
|
|
--
|
|
|
|
-- _stopApplicationNeedsRoot = True
|
|
|
|
--
|
|
|
|
-- def _getExtraAmStartArgs(self):
|
|
|
|
-- return []
|
|
|
|
--
|
|
|
|
-- def launchApplication(self, appName, activityName, intent, url=None,
|
|
|
|
-- extras=None, wait=True, failIfRunning=True):
|
|
|
|
-- """
|
|
|
|
-- Launches an Android application
|
|
|
|
--
|
|
|
|
-- :param appName: Name of application (e.g. `com.android.chrome`)
|
|
|
|
-- :param activityName: Name of activity to launch (e.g. `.Main`)
|
|
|
|
-- :param intent: Intent to launch application with
|
|
|
|
-- :param url: URL to open
|
|
|
|
-- :param extras: Dictionary of extra arguments to launch application with
|
|
|
|
-- :param wait: If True, wait for application to start before returning
|
|
|
|
-- :param failIfRunning: Raise an exception if instance of application is already running
|
|
|
|
-- """
|
|
|
|
--
|
|
|
|
-- # If failIfRunning is True, we throw an exception here. Only one
|
|
|
|
-- # instance of an application can be running at once on Android,
|
|
|
|
-- # starting a new instance may not be what we want depending on what
|
|
|
|
-- # we want to do
|
|
|
|
-- if failIfRunning and self.processExist(appName):
|
|
|
|
-- raise DMError("Only one instance of an application may be running "
|
|
|
|
-- "at once")
|
|
|
|
--
|
|
|
|
-- acmd = ["am", "start"] + self._getExtraAmStartArgs() + \
|
|
|
|
-- ["-W" if wait else '', "-n", "%s/%s" % (appName, activityName)]
|
|
|
|
--
|
|
|
|
-- if intent:
|
|
|
|
-- acmd.extend(["-a", intent])
|
|
|
|
--
|
|
|
|
-- if extras:
|
|
|
|
-- for (key, val) in extras.iteritems():
|
|
|
|
-- if type(val) is int:
|
|
|
|
-- extraTypeParam = "--ei"
|
|
|
|
-- elif type(val) is bool:
|
|
|
|
-- extraTypeParam = "--ez"
|
|
|
|
-- else:
|
|
|
|
-- extraTypeParam = "--es"
|
|
|
|
-- acmd.extend([extraTypeParam, str(key), str(val)])
|
|
|
|
--
|
|
|
|
-- if url:
|
|
|
|
-- acmd.extend(["-d", url])
|
|
|
|
--
|
|
|
|
-- # shell output not that interesting and debugging logs should already
|
|
|
|
-- # show what's going on here... so just create an empty memory buffer
|
|
|
|
-- # and ignore (except on error)
|
|
|
|
-- shellOutput = StringIO.StringIO()
|
|
|
|
-- if self.shell(acmd, shellOutput) == 0:
|
|
|
|
-- return
|
|
|
|
--
|
|
|
|
-- shellOutput.seek(0)
|
|
|
|
-- raise DMError("Unable to launch application (shell output: '%s')" % shellOutput.read())
|
|
|
|
--
|
|
|
|
-- def launchFennec(self, appName, intent="android.intent.action.VIEW",
|
|
|
|
-- mozEnv=None, extraArgs=None, url=None, wait=True,
|
|
|
|
-- failIfRunning=True):
|
|
|
|
-- """
|
|
|
|
-- Convenience method to launch Fennec on Android with various debugging
|
|
|
|
-- arguments
|
|
|
|
--
|
|
|
|
-- :param appName: Name of fennec application (e.g. `org.mozilla.fennec`)
|
|
|
|
-- :param intent: Intent to launch application with
|
|
|
|
-- :param mozEnv: Mozilla specific environment to pass into application
|
|
|
|
-- :param extraArgs: Extra arguments to be parsed by fennec
|
|
|
|
-- :param url: URL to open
|
|
|
|
-- :param wait: If True, wait for application to start before returning
|
|
|
|
-- :param failIfRunning: Raise an exception if instance of application is already running
|
|
|
|
-- """
|
|
|
|
-- extras = {}
|
|
|
|
--
|
|
|
|
-- if mozEnv:
|
|
|
|
-- # mozEnv is expected to be a dictionary of environment variables: Fennec
|
|
|
|
-- # itself will set them when launched
|
|
|
|
-- for (envCnt, (envkey, envval)) in enumerate(mozEnv.iteritems()):
|
|
|
|
-- extras["env" + str(envCnt)] = envkey + "=" + envval
|
|
|
|
--
|
|
|
|
-- # Additional command line arguments that fennec will read and use (e.g.
|
|
|
|
-- # with a custom profile)
|
|
|
|
-- if extraArgs:
|
|
|
|
-- extras['args'] = " ".join(extraArgs)
|
|
|
|
--
|
|
|
|
-- self.launchApplication(appName, "org.mozilla.gecko.BrowserApp", intent, url=url,
|
|
|
|
-- extras=extras,
|
|
|
|
-- wait=wait, failIfRunning=failIfRunning)
|
|
|
|
--
|
|
|
|
-- def getInstalledApps(self):
|
|
|
|
-- """
|
|
|
|
-- Lists applications installed on this Android device
|
|
|
|
--
|
|
|
|
-- Returns a list of application names in the form [ 'org.mozilla.fennec', ... ]
|
|
|
|
-- """
|
|
|
|
-- output = self.shellCheckOutput(["pm", "list", "packages", "-f"])
|
|
|
|
-- apps = []
|
|
|
|
-- for line in output.splitlines():
|
|
|
|
-- # lines are of form: package:/system/app/qik-tmo.apk=com.qiktmobile.android
|
|
|
|
-- apps.append(line.split('=')[1])
|
|
|
|
--
|
|
|
|
-- return apps
|
|
|
|
--
|
|
|
|
-- def stopApplication(self, appName):
|
|
|
|
-- """
|
|
|
|
-- Stops the specified application
|
|
|
|
--
|
|
|
|
-- For Android 3.0+, we use the "am force-stop" to do this, which is
|
|
|
|
-- reliable and does not require root. For earlier versions of Android,
|
|
|
|
-- we simply try to manually kill the processes started by the app
|
|
|
|
-- repeatedly until none is around any more. This is less reliable and
|
|
|
|
-- does require root.
|
|
|
|
--
|
|
|
|
-- :param appName: Name of application (e.g. `com.android.chrome`)
|
|
|
|
-- """
|
|
|
|
-- version = self.shellCheckOutput(["getprop", "ro.build.version.sdk"])
|
|
|
|
-- if int(version) >= version_codes.HONEYCOMB:
|
|
|
|
-- self.shellCheckOutput(["am", "force-stop", appName],
|
|
|
|
-- root=self._stopApplicationNeedsRoot)
|
|
|
|
-- else:
|
|
|
|
-- num_tries = 0
|
|
|
|
-- max_tries = 5
|
|
|
|
-- while self.processExist(appName):
|
|
|
|
-- if num_tries > max_tries:
|
|
|
|
-- raise DMError("Couldn't successfully kill %s after %s "
|
|
|
|
-- "tries" % (appName, max_tries))
|
|
|
|
-- self.killProcess(appName)
|
|
|
|
-- num_tries += 1
|
|
|
|
--
|
|
|
|
-- # sleep for a short duration to make sure there are no
|
|
|
|
-- # additional processes in the process of being launched
|
|
|
|
-- # (this is not 100% guaranteed to work since it is inherently
|
|
|
|
-- # racey, but it's the best we can do)
|
|
|
|
-- time.sleep(1)
|
|
|
|
--
|
|
|
|
--
|
|
|
|
--class DroidADB(DeviceManagerADB, DroidMixin):
|
|
|
|
--
|
|
|
|
-- _stopApplicationNeedsRoot = False
|
|
|
|
--
|
|
|
|
-- def getTopActivity(self):
|
|
|
|
-- package = None
|
|
|
|
-- data = None
|
|
|
|
-- try:
|
|
|
|
-- # Increased timeout to 60 seconds after intermittent timeouts at 30.
|
|
|
|
-- data = self.shellCheckOutput(
|
|
|
|
-- ["dumpsys", "window", "windows"], timeout=60)
|
|
|
|
-- except Exception:
|
|
|
|
-- # dumpsys seems to intermittently fail (seen on 4.3 emulator), producing
|
|
|
|
-- # no output.
|
|
|
|
-- return ""
|
|
|
|
-- # "dumpsys window windows" produces many lines of input. The top/foreground
|
|
|
|
-- # activity is indicated by something like:
|
|
|
|
-- # mFocusedApp=AppWindowToken{483e6db0 token=HistoryRecord{484dcad8 com.mozilla.something/.something}} # noqa
|
|
|
|
-- # or, on other devices:
|
|
|
|
-- # FocusedApplication: name='AppWindowToken{41a65340 token=ActivityRecord{418fbd68 org.mozilla.fennec_mozdev/org.mozilla.gecko.BrowserApp}}', dispatchingTimeout=5000.000ms # noqa
|
|
|
|
-- # Extract this line, ending in the forward slash:
|
|
|
|
-- m = re.search('mFocusedApp(.+)/', data)
|
|
|
|
-- if not m:
|
|
|
|
-- m = re.search('FocusedApplication(.+)/', data)
|
|
|
|
-- if m:
|
|
|
|
-- line = m.group(0)
|
|
|
|
-- # Extract package name: string of non-whitespace ending in forward slash
|
|
|
|
-- m = re.search('(\S+)/$', line)
|
|
|
|
-- if m:
|
|
|
|
-- package = m.group(1)
|
|
|
|
-- if not package:
|
|
|
|
-- # On some Android 4.4 devices, when the home screen is displayed,
|
|
|
|
-- # dumpsys reports "mFocusedApp=null". Guard against this case and
|
|
|
|
-- # others where the focused app can not be determined by returning
|
|
|
|
-- # an empty string.
|
|
|
|
-- package = ""
|
|
|
|
-- return package
|
|
|
|
--
|
|
|
|
-- def getAppRoot(self, packageName):
|
|
|
|
-- """
|
|
|
|
-- Returns the root directory for the specified android application
|
|
|
|
-- """
|
|
|
|
-- # relying on convention
|
|
|
|
-- return '/data/data/%s' % packageName
|
|
|
|
-diff --git a/testing/mozbase/mozdevice/setup.py b/testing/mozbase/mozdevice/setup.py
|
|
|
|
---- a/testing/mozbase/mozdevice/setup.py
|
|
|
|
-+++ b/testing/mozbase/mozdevice/setup.py
|
|
|
|
-@@ -29,12 +29,10 @@ setup(name=PACKAGE_NAME,
|
|
|
|
- url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
|
|
|
|
- license='MPL',
|
|
|
|
- packages=['mozdevice'],
|
|
|
|
- include_package_data=True,
|
|
|
|
- zip_safe=False,
|
|
|
|
- install_requires=deps,
|
|
|
|
- entry_points="""
|
|
|
|
- # -*- Entry points: -*-
|
|
|
|
-- [console_scripts]
|
|
|
|
-- dm = mozdevice.dmcli:cli
|
|
|
|
- """,
|
|
|
|
- )
|
|
|
|
-diff --git a/testing/mozbase/moztest/moztest/output/base.py.1440714-17.later b/testing/mozbase/moztest/moztest/output/base.py.1440714-17.later
|
|
|
|
-new file mode 100644
|
|
|
|
---- /dev/null
|
|
|
|
-+++ b/testing/mozbase/moztest/moztest/output/base.py.1440714-17.later
|
|
|
|
-@@ -0,0 +1,20 @@
|
|
|
|
-+--- base.py
|
|
|
|
-++++ base.py
|
|
|
|
-+@@ -6,17 +6,16 @@ from __future__ import absolute_import,
|
|
|
|
-+
|
|
|
|
-+ from contextlib import closing
|
|
|
|
-+ from StringIO import StringIO
|
|
|
|
-+
|
|
|
|
-+ try:
|
|
|
|
-+ from abc import abstractmethod
|
|
|
|
-+ except ImportError:
|
|
|
|
-+ # abc is python 2.6+
|
|
|
|
-+- # from https://github.com/mozilla/mozbase/blob/master/mozdevice/mozdevice/devicemanager.py
|
|
|
|
-+ def abstractmethod(method):
|
|
|
|
-+ line = method.func_code.co_firstlineno
|
|
|
|
-+ filename = method.func_code.co_filename
|
|
|
|
-+
|
|
|
|
-+ def not_implemented(*args, **kwargs):
|
|
|
|
-+ raise NotImplementedError('Abstract method %s at File "%s", '
|
|
|
|
-+ 'line %s should be implemented by a concrete class' %
|
|
|
|
-+ (repr(method), filename, line))
|
|
|
|
diff --git a/testing/xpcshell/runxpcshelltests.py b/testing/xpcshell/runxpcshelltests.py
|
|
diff --git a/testing/xpcshell/runxpcshelltests.py b/testing/xpcshell/runxpcshelltests.py
|
|
--- a/testing/xpcshell/runxpcshelltests.py
|
|
--- a/testing/xpcshell/runxpcshelltests.py
|
|
+++ b/testing/xpcshell/runxpcshelltests.py
|
|
+++ b/testing/xpcshell/runxpcshelltests.py
|