When using MonkeyRunner, every so often I get an error like:
120830 18:39:32.755:S [MainThread] [com.android.chimpchat.adb.AdbChimpDevice] Unable to get variable: display.density
120830 18:39:32.755:S [MainThread] [com.android.chimpchat.adb.AdbChimpDevice]java.net.SocketException: Connection reset
From what I've read, sometimes the adb connection goes bad, and you need to reconnect. The only problem is, I'm not able to catch the SocketException. I'll wrap my code like so:
try:
density = self.device.getProperty('display.density')
except:
print 'This will never print.'
But the exception is apparently not raised all the way to the caller. I've verified that MonkeyRunner/jython can catch Java exceptions the way I'd expect:
>>> from java.io import FileInputStream
>>> def test_java_exceptions():
... try:
... FileInputStream('bad mojo')
... except:
... print 'Caught it!'
...
>>> test_java_exceptions()
Caught it!
How can I deal with these socket exceptions?
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=None):
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
Edit: The signal seems to require 2 parameters, not sure it's always the case, made the third optional.
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')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With