how to fail the step explicitly in behave step implementation - python

I want to explicitly fail the step in behave when I encounter an exception
eg. I am writing the code according to behave documentation -
from behave import *
#when('verify test fails.*?(?P<param_dict>.*)')
def test_logger(context, param_dict):
try:
logger.info("testing the logger. this is info message")
logger.info(1/0)
except Exception as e:
logger.error("arrived at exception: "+str(e))
fail("failed with exception: "+str(e))
but it throws this error:
NameError: name 'fail' is not defined
I tried other ways too, but nothing works
eg. context.failed = True (did not work either)
If I do not try to fail explicitly, final test result becomes PASS even if it goes in exception block ...which is weird.

context.failed is only an attribute set by Behave and doesn't do anything as is. It's an information attribute, and while you can use it to determine a fail-case and throw an assertion error, it will not do anything on it's own. See context.failed
As for the fail method you've mentioned, it is probably from the unittest module, as seen here. This module is used in Behave's development tests (see their Github) as well. I'll agree though, that this should be clarified in their documentation.
To fix your error you'd need to import the unittest module. To explicitly fail the step, you'd just raise the exception after you've logged your message, something like this:
except Exception as e:
logger.error("arrived at exception: "+str(e))
fail("failed with exception: "+str(e))
raise

As #Verv mentioned in their answer, a behave step will be marked as failed whenever an exception is thrown, so you could just re-raise the exception.
However, behave will show the backtrace for normal exceptions, whereas you probably just want to show a specific failure message.
For that, raise an AssertionError. In your code, that would look like this:
except Exception as e:
logger.error("arrived at exception: " + str(e))
raise AssertionError("failed with exception: " + str(e))
If behave encounters an AssertionError, it will fail the step, display the message you instantiate the error with, but not display other exception stuff.

Related

Pytest - Altering the outcome based on the exception error raised

I was wondering how I could alter a pytest test outcome (from a fail to a skip) in the case that my error message includes a specific string.
Occasionally we get test failures using appium where the response from the appium server is a 500 error with the failure message: "An unknown server-side error occurred while processing the command." Its an issue that we need to solve, but for the meantime we want to basically say, if the test failed because of an error message similar to that, skip the test instead of failing it.
Ive considered and tried something like this:
def pytest_runtest_setup(item):
excinfo = None
try:
item.obj()
except Exception as e:
excinfo = sys.exc_info()
if excinfo and "An unknown server-side error occurred while processing the command." in str(excinfo[1]):
pytest.skip("Skipping test due to error message")
And this obviously won't work.
But I was hoping for a similar approach.
The successful answer:
In order for me to get this working #Teejay pointed out below I needed to use the runtest_call hook and assess the message there. Currently working well in my test suite!
#pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
output = yield
if output.excinfo:
for potential_error_message in expected_failure_messages.keys():
if output._excinfo[1].stacktrace and potential_error_message in output._excinfo[1].stacktrace:
pytest.skip(reason=expected_failure_messages[potential_error_message])
I recommend utilizing a hook wrapper to inspect the exception raised and act accordingly
#pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(item):
output = yield
if output.excinfo:
# Additional logic to target specific error
pytest.skip()
Your sketched idea is close to workable. I'd skip using sys.exc_info() and just inspect the str() value of the exception, and I'd restrict the classes of exception caught to the smallest set that covers the failures you're trying to ignore.
Something like:
try:
item.obj()
except OSError as e: # ideally a narrower subclass or tuple of classes
if 'An unknown server-side error occurred while processing the command.' in str(e):
pytest.skip(f'Skipping test due to error message {e}')
else:
raise e
The only additional logical change I've made is moving the if/skip into the exception handler and re-raising if it doesn't match the message you're expecting.
Restricting the classes of errors matched is a best practice to avoid catching situations you didn't intend to catch. It's probably harmless to over-catch here where you're inspecting the message, just a good habit to cultivate. But it might also let you identify a specific field of the exception class to check rather than just the string representation - eg OSError has the strerror attribute containing the error message from the OS, so if you've limited your except block to catching just those you know you'll have that attribute available.
I chose to include the exception in the skip message, you might decide differently if they're uninformative.

Sys.exit(1) after caught exception in python or raise exception again

I wonder how to best handle exceptions in python and to inform the user about it.
I came across the following ideas:
Option 1:
try:
do something
except MyError as e:
logger.error(e)
sys.exit(1)
next code
Option 2:
try:
do something
except MyError as e:
logger.error(e)
raise e
next code
Option: 3
try:
do something
except MyError as e:
logger.error(e)
else:
next code
All three differ in behavior, and you choose which one makes sense in your scenario.
In option #1, you're saying "This is a fatal error, but I want to die 'cleanly' instead of dumping a full traceback to screen."
With option #2, you're saying "I want to log the error, but I'm not handling it; maybe someone higher up the stack will?"; if nobody catches it, it behaves similarly to option #1, aside from (by default) dumping a traceback to the terminal.
With option #3, you're saying "This isn't a fatal error, and we can keep going even if it happens, but certain actions should only be done when the error doesn't occur."
I will note your option #2 is (usually) wrong; you want plain raise to reraise the exception without resetting the traceback or causing exception chaining (which raise e would do), making it behave as if you never caught the exception (aside from the logging output).

Python exceptions - catching all exceptions but the one expected

I am working on a simple automation script in Python, which could throw exceptions in various spots. In each of them I would like to log a specific message and exit the program. In order to do that, I raise SystemExit after catching the exception and handling it (performing specific logging operations and such).
In the top-level calling of main, I do the following:
if __name__ == "__main__":
try:
main()
except SystemExit: # handled exception
sys.exit(1)
except: # any unhandled exception
logging.error('Unexpected error: ', exc_info=True)
sys.exit(2)
However, using a bare except is something frowned upon. Is using an "exception tree" where I use a bare except to specify "anything but the exception that I've handled" a nonstandard way? Is there a better way to achieve this? I would still like to log these unhandled exceptions, even if they were not handled.
Edit: SystemExit is raised to note that an exception has been handled - no matter what the exception is in my case, I always want to stop running the scripts as any failure should result in an absolute failure.
The main reason I'm asking this is that PEP8 seems to consider using a bare except as an error, and even though I could use except BaseException, it should be just a syntactic difference. Is one way more standard than the other or is there another standard route of achieving this?
Bare exceptions trap things you do not want to trap, such as GeneratorExit. Do it this way:
except Exception as details:
logging.error('Unexpected error: {0}'.format(details))
The main issue with a bare except is that it can catch things like SystemExit and KeyboardInterrupt which are not standard 'code' errors and shouldn't usually be handled in the same way as an exception generated by your code. Using the Exception class doesn't cover those cases as they do not inherit from it, so it is more than a syntax difference.
https://docs.python.org/2/howto/doanddont.html#except
https://docs.python.org/3.1/howto/doanddont.html#except
If you want to handle those specific cases, then it is better to do so explicitly as you have done for SystemExit.
This worked for me:
try:
<code>
raise Exception("my error")
except Exception as e:
raise e
If my error occurs then the error message "my errror" is seen. If an unknown exception occurs then it displays the default exception handler's text. In either case an exception is raised and the script is halted.

python ignore whichever line that causes error and continue running the code after that line

I understand the try/except method. What I'm trying to do is:
try:
some_func1() #potentially raises error too
do_something_else() #error was raised
continue_doing_something_else() #continues here after handling error
except:
pass
In the above code, when the error is raised at do_something_else(), the error is handled but then the try statement is exited.
What I want is for python to continue whatever code that is after the line that causes the error. Assuming that an error can happen anywhere in the try statement so I can't just wrap the try/except around do_something_else() itself, is there a way to do this in python?
What you want to do (try with restart) is not possible in Python. Lisp can do it (http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html), and you can implement it in Scheme using call/cc.
Just put the code that you want to do after the possible exception after the except. You may want to use finally (see https://docs.python.org/3/tutorial/errors.html for the documentation):
try:
some_func1()
do_something_else() #error was raised
except:
handle_exception()
finally:
continue_doing_something_else() #continues here after handling error or if no error occurs
if continue_doing_something_else() can also throw exceptions, then put that in a try/except too:
try:
some_func1()
do_something_else() #error was raised
except:
handle_exception1()
try:
continue_doing_something_else()
except:
handle_exception2()
finally:
any_cleanup()
as a general rule, your exception handling should be kept as small in scope as possible while remaining sensible, consider excepting only the 'expected' exceptions and not all of them (e.g. except OSError: when attempting to open files)

Python reraise/recatch exception

I would like to know if it is possible in python to raise an exception in one except block and catch it in a later except block. I believe some other languages do this by default.
Here is what it would look like"
try:
something
except SpecificError as ex:
if str(ex) = "some error I am expecting"
print "close softly"
else:
raise
except Exception as ex:
print "did not close softly"
raise
I want the raise in the else clause to trigger the final except statement.
In actuality I am not printing anything but logging it and I want to log more in the case that it is the error message that I am not expecting. However this additional logging will be included in the final except.
I believe one solution would be to make a function if it does not close softly which is called in the final except and in the else clause. But that seems unnecessary.
What about writing 2 try...except blocks like this:
try:
try:
something
except SpecificError as ex:
if str(ex) == "some error I am expecting"
print "close softly"
else:
raise ex
except Exception as ex:
print "did not close softly"
raise ex
Only a single except clause in a try block is invoked. If you want the exception to be caught higher up then you will need to use nested try blocks.
As per python tutorial there is one and only one catched exception per one try statement.
You can find pretty simple example in tutorial that will also show you how to correctly use error formatting.
Anyway why do you really need second one? Could you provide more details on this?
You can do this using the six package.
Six provides simple utilities for wrapping over differences between Python 2 and Python 3.
Specifically, see six.reraise:
Reraise an exception, possibly with a different traceback. In the simple case, reraise(*sys.exc_info()) with an active exception (in an except block) reraises the current exception with the last traceback. A different traceback can be specified with the exc_traceback parameter. Note that since the exception reraising is done within the reraise() function, Python will attach the call frame of reraise() to whatever traceback is raised.

Categories