How do I catch SocketExceptions in MonkeyRunner?

混江龙づ霸主 提交于 2019-12-04 06:12:49
Woxxy

You will get that error every odd time you start MonkeyRunner because the monkey --port 12345 command on the device isn't stopped when your script stops. It is a bug in monkey.

A nicer way to solve this issue is killing monkey when SIGINT is sent to your script (when you ctrl+c). In other words: $ killall com.android.commands.monkey.

Quick way to do it:

from sys, signal
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

device = None

def execute():
    device = MonkeyRunner.waitForConnection()
    # your code

def exitGracefully(self, signum, frame):
    signal.signal(signal.SIGINT, signal.getsignal(signal.SIGINT))
    device.shell('killall com.android.commands.monkey')
    sys.exit(1)

if __name__ == '__main__'
    signal.signal(signal.SIGINT, exitGracefully)
    execute()

Edit: as an addendum, I also found a way to notice the Java errors: Monkey Runner throwing socket exception broken pipe on touuch

Below is the workaround I ended up using. Any function that can suffer from adb failures just needs to use the following decorator:

from subprocess import call, PIPE, Popen
from time import sleep

def check_connection(f):
    """
    adb is unstable and cannot be trusted.  When there's a problem, a
    SocketException will be thrown, but caught internally by MonkeyRunner
    and simply logged.  As a hacky solution, this checks if the stderr log 
    grows after f is called (a false positive isn't going to cause any harm).
    If so, the connection will be repaired and the decorated function/method
    will be called again.

    Make sure that stderr is redirected at the command line to the file
    specified by config.STDERR. Also, this decorator will only work for 
    functions/methods that take a Device object as the first argument.
    """
    def wrapper(*args, **kwargs):
        while True:
            cmd = "wc -l %s | awk '{print $1}'" % config.STDERR
            p = Popen(cmd, shell=True, stdout=PIPE)
            (line_count_pre, stderr) = p.communicate()
            line_count_pre = line_count_pre.strip()

            f(*args, **kwargs)

            p = Popen(cmd, shell=True, stdout=PIPE)
            (line_count_post, stderr) = p.communicate()
            line_count_post = line_count_post.strip()

            if line_count_pre == line_count_post:
                # the connection was fine
                break
            print 'Connection error. Restarting adb...'
            sleep(1)
            call('adb kill-server', shell=True)
            call('adb start-server', shell=True)
            args[0].connection = MonkeyRunner.waitForConnection()

    return wrapper

Because this may create a new connection, you need to wrap your current connection in a Device object so that it can be changed. Here's my Device class (most of the class is for convenience, the only thing that's necessary is the connection member:

class Device:
    def __init__(self):
        self.connection = MonkeyRunner.waitForConnection()
        self.width = int(self.connection.getProperty('display.width'))
        self.height = int(self.connection.getProperty('display.height'))
        self.model = self.connection.getProperty('build.model')

    def touch(self, x, y, press=MonkeyDevice.DOWN_AND_UP):
        self.connection.touch(x, y, press)

An example on how to use the decorator:

@check_connection
def screenshot(device, filename):
    screen = device.connection.takeSnapshot()
    screen.writeToFile(filename + '.png', 'png')
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!