How do I catch SocketExceptions in MonkeyRunner? - jython

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')

Related

how to read the console output in python without executing any command

I have an API which gets the success or error message on console.I am new to python and trying to read the response. Google throws so many examples to use subprocess but I dont want to run,call any command or sub process. I just want to read the output after below API call.
This is the response in console when success
17:50:52 | Logged in!!
This is the github link for the sdk and documentation
https://github.com/5paisa/py5paisa
This is the code
from py5paisa import FivePaisaClient
email = "myemailid#gmail.com"
pw = "mypassword"
dob = "mydateofbirth"
cred={
"APP_NAME":"app-name",
"APP_SOURCE":"app-src",
"USER_ID":"user-id",
"PASSWORD":"pw",
"USER_KEY":"user-key",
"ENCRYPTION_KEY":"enc-key"
}
client = FivePaisaClient(email=email, passwd=pw, dob=dob,cred=cred)
client.login()
In general it is bad practice to get a value from STDOUT. There are some ways but it's pretty tricky (it's not made for it). And the problem doesn't come from you but from the API which is wrongly designed, it should return a value e.g. True or False (at least) to tell you if you logged in, and they don't do it.
So, according to their documentation it is not possible to know if you're logged in, but you may be able to see if you're logged in by checking the attribute client_code in the client object.
If client.client_code is equal to something then it should be logged in and if it is equal to something else then not. You can try comparing it's value when you successfully login or when it fails (wrong credential for instance). Then you can put a condition : if it is None or False or 0 (you will have to see this by yourself) then it is failed.
Can you try doing the following with a successful and failed login:
client.login()
print(client.client_code)
Source of the API:
# Login function :
# (...)
message = res["body"]["Message"]
if message == "":
log_response("Logged in!!")
else:
log_response(message)
self._set_client_code(res["body"]["ClientCode"])
# (...)
# _set_client_code function :
def _set_client_code(self, client_code):
try:
self.client_code = client_code # <<<< That's what we want
except Exception as e:
log_response(e)
Since this questions asks how to capture "stdout" one way you can accomplish this is to intercept the log message before it hits stdout.
The minimum code to capture a log message within a Python script looks this:
#!/usr/bin/env python3
import logging
logger = logging.getLogger(__name__)
class RequestHandler(logging.Handler):
def emit(self, record):
if record.getMessage().startswith("Hello"):
print("hello detected")
handler = RequestHandler()
logger.addHandler(handler)
logger.warning("Hello world")
Putting it all together you may be able to do something like this:
import logging
from py5paisa import FivePaisaClient
email = "myemailid#gmail.com"
pw = "mypassword"
dob = "mydateofbirth"
cred={
"APP_NAME":"app-name",
"APP_SOURCE":"app-src",
"USER_ID":"user-id",
"PASSWORD":"pw",
"USER_KEY":"user-key",
"ENCRYPTION_KEY":"enc-key"
}
client = FivePaisaClient(email=email, passwd=pw, dob=dob,cred=cred)
class PaisaClient(logging.Handler):
def __init__():
self.loggedin = False # this is the variable we can use to see if we are "logged in"
def emit(self, record):
if record.getMessage().startswith("Logged in!!")
self.loggedin = True
def login():
client.login()
logging.getLogger(py5paisa) # get the logger for the py5paisa library
# tutorial here: https://betterstack.com/community/questions/how-to-disable-logging-from-python-request-library/
logging.basicConfig(handlers=[PaisaClient()], level=0, force=True)
c = PaisaClient()
c.login()

SSH and Ping to hosts concurrently with Python asyncio?

I'm trying to SSH/Ping to hosts concurrently, but I don't see any result so for, probably my implementation isn't correct. This is what I have so far. Any idea appreciated.
import paramiko
import time
import asyncio
import subprocess
async def sshTest(ipaddress,deviceUsername,devicePassword,sshPort): #finalDict
try:
print("Performing SSH Connection to the device")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(ipaddress, username=deviceUsername, password=devicePassword, port=sshPort, look_for_keys=False, allow_agent=False)
print("Channel established")
except Exception as e:
print(e)
async def pingf(ip):
p1 = subprocess.Popen(['ping', '-c','5', ip], stdout=subprocess.PIPE)
output = p1.communicate()[0]
print(output)
async def main():
taskA = loop.create_task(sshTest('192.168.255.68','admin','admin','22'))
taskB = loop.create_task(sshTest('192.168.254.108','admin','admin','22'))
taskC = loop.create_task(sshTest('192.168.249.134','admin','admin','22'))
taskD = loop.create_task(sshTest('192.168.254.108','admin','admin','22'))
task1 = loop.create_task(pingf('192.168.255.68'))
task2 = loop.create_task(pingf('192.168.254.108'))
task3 = loop.create_task(pingf('192.168.249.134'))
task4 = loop.create_task(pingf('192.168.254.108'))
await asyncio.wait([taskA,taskB,taskC,taskD,task1,task2,task3,task4])
if __name__ == "__main__":
start = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
end = time.time()
print("The time of execution of above program is :", end-start)
Asyncio is a form of cooperative multitasking. This means that in order for tasks to run concurrently, a task must explicitly yield control back to the scheduler, which in Python means "your tasks need to await on something".
Neither of your tasks ever calls await, so they're not going to run concurrently. What you have right now is going to run serially.
If you want to run ssh connections concurrently, you're going to have to either:
Replace paramiko with something like AsyncSSH, which is written to work with asyncio, or
Use threading or multiprocessing to parallelize your tasks, rather than using asyncio.
Additionally, if you're working with asyncio, anything that involves running an external command (such as your pingf task) is going to need to use asyncio's run_in_executor method.
For the example you've shown here, I would suggest instead using the concurrent.futures module. Your code might end up looking something like this (I've modified the code to run in my test environment and given the sshTest task something to do beyond simply connecting):
import concurrent.futures
import paramiko
import asyncio
import subprocess
def sshTest(ipaddress, deviceUsername, devicePassword, sshPort): # finalDict
try:
print("Performing SSH Connection to the device")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
ipaddress,
username=deviceUsername,
password=devicePassword,
port=sshPort,
look_for_keys=True,
allow_agent=True,
)
stdin, stdout, stderr = client.exec_command("sh -c 'sleep 2; uptime'")
output = stdout.read()
return output
except Exception:
return "failed to connect"
def pingf(ip):
output = subprocess.check_output(["ping", "-c", "5", ip])
return output
def main():
futures = []
with concurrent.futures.ThreadPoolExecutor() as pool:
futures.append(pool.submit(sshTest, "localhost", "root", "admin", "2200"))
futures.append(pool.submit(sshTest, "localhost", "root", "admin", "2201"))
futures.append(pool.submit(sshTest, "localhost", "root", "admin", "2202"))
futures.append(pool.submit(pingf, "192.168.1.1"))
futures.append(pool.submit(pingf, "192.168.1.5"))
futures.append(pool.submit(pingf, "192.168.1.254"))
for future in concurrent.futures.as_completed(futures):
print("return value from task:", future.result())
if __name__ == "__main__":
main()

How can I know the command used? - Discord.py

I am trying to make a errorhandling for my Discord.py, how do I know what command was used for the error to pop up?
#bot.event
async def on_command_error(ctx, error):
print("error: ",error)
if search("not found", str(error)):
c_f = random.choice([f"`{command used}` was not found, silly.", "Ehm.. Since when do we have `{command used}`?", "I don't know what `{command used}` is?"])
embed=discord.Embed(title=c_f, description=f"Please use existing commands. {ctx.author.mention}", color=error_color)
embed.timestamp = datetime.utcnow()
embed.set_footer(text=bot_name, icon_url=icon_uri)
await ctx.send(embed=embed)
elif search("cooldown", str(error)):
c_d = random.choice(["Did you drink energy drinks!?", "Why are you stressing, buddy.", "Duhh, wait, you're on cooldown!"])
second_remain = round(error.retry_after, 1)
embed=discord.Embed(title=c_d, description=f"Try again after {second_remain}s. {ctx.author.mention}", color=error_color)
embed.timestamp = datetime.utcnow()
embed.set_footer(text=bot_name, icon_url=icon_uri)
await ctx.send(embed=embed)
else:
raise error
Any attribute I can use?
You can use ctx.command
#bot.event
async def on_command_error(ctx, exception):
error = getattr(exception, "original", exception)
if hasattr(ctx.command, "on_error"): # If a command has it's own handler
return
elif isinstance(error, CommandNotFound):
return
if isinstance(error, discord.CommandInvokeError):
print(ctx.command)
Your solution is to add them to the command specifically, this also means it can help diagnose an issue with a command more exact.
You can also add any error events to the specific listener, just like how you done it for all commands, instead add them individually.
#bot.command()
async def command_name(ctx):
# ...
#command_name.error
async def command_name_error(ctx, error):
if isinstance(error, commands.CommandInvokeError):
await ctx.send("An error from this command" + error)
With #command_name.error put your command name before the .error, then this makes an error listener for that command, if it produces an error.

How to handle "flood wait" errors when using telethon.sync?

Looks like sync version of the client doesn't throw any errors?
What is the right way to handle errors when working with telethon.sync?
the code below results in client going to "sleep", but no errors are cought.
I tried to do the same with explicit exception for FloodWaitError, it doesn't solve the issue.
from telethon.sync import TelegramClient
from telethon.tl.functions.channels import GetFullChannelRequest
if __name__ == '__main__':
setup_logging(level=logging.INFO)
tg = TelegramClient(
'anon',
api_id=config.API_ID,
api_hash=config.API_HASH,
)
with tg as client:
try:
result = client(GetFullChannelRequest(-1001100118939))
except ValueError as e:
print(e)
break;
# print('Flood wait for ', e.seconds)
# time.sleep(e.seconds)
print(result)
telethon.sync doesn't change the behavior of exceptions. However, FloodWaitError is not a ValueError, so your except won't catch it. The following will work:
from telethon import errors
try:
...
except errors.FloodWaitError as e:
print('Flood wait for ', e.seconds)
Note that the library automatically sleeps if the flood error is less than a minute by default, in which case it will wait and not raise for convenience.

Detect whether test has failed within fixture

I am debugging an intermittent test failure. For this purposes I want to dump a lot of debug information if a test failed. Dumping debug stuff is quite slow process which produces a lot of data, so I do not want to do this for every test.
I am using pytest and yield autouse fixture should work great
#pytest.yield_fixture(scope="function", autouse=True)
def dump_on_failue(request):
prepare_debug_dump()
yield
if test_failed(request):
debug_dump()
The problem is that I can't figure out how do I detect whether test has failed or not. There was a questions already and even note on pytest website:
if request.node.rep_setup.failed:
print ("setting up a test failed!", request.node.nodeid)
elif request.node.rep_setup.passed:
if request.node.rep_call.failed:
print ("executing test failed", request.node.nodeid)
Unfortunately this code does not work anymore. There are no rep_setup and rep_calls symbols in node object. I tried to dig request and node object, but no luck.
Anybody knows how to detect whether test failed?
There are no rep_setup and rep_calls symbols in node object.
No.rep_setup and rep_calls symbols are still there.
Add this code into your root conftest.py. It will check pass/fail for every test function.
import pytest
#pytest.mark.tryfirst
def pytest_runtest_makereport(item, call, __multicall__):
rep = __multicall__.execute()
setattr(item, "rep_" + rep.when, rep)
return rep
#pytest.fixture(scope='function', autouse=True)
def test_debug_log(request):
def test_result():
if request.node.rep_setup.failed:
print ("setting up a test failed!", request.node.nodeid)
elif request.node.rep_setup.passed:
if request.node.rep_call.failed:
print ("executing test failed", request.node.nodeid)
request.addfinalizer(test_result)
I use something like this, maybe it can be useful in your case:
#pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item):
outcome = yield
rep = outcome.get_result()
if rep.failed:
if rep.when == "setup":
print("Setup failed!")
elif rep.when == "call":
print("Test method failed")
elif rep.passed and rep.when == "call":
print("Test {} passed.".format(item.name))