How Get the List of Errors Thrown by a Function

How get the list of errors thrown by a function?

When the Swift docs says a function throws, they mean that it throws an ErrorType (in Cocoa APIs usually an NSError), not an exception.

Consider the following do-try-catch flow for NSFileManager's createDirectoryAtPath:

let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]

do {
try NSFileManager.defaultManager().createDirectoryAtPath(documentsPath, withIntermediateDirectories: false, attributes: nil)
} catch {
// 'error' variable automatically populated
print(error)
print(error.dynamicType)
}

createDirectoryAtPath will fail because the documents directory already exists. Logging the dynamicType of the error shows that it is in fact an NSError object:

Error Domain=NSCocoaErrorDomain Code=516 "The file “Documents” couldn’t be saved in the folder “35B0B3BF-D502-4BA0-A991-D07568AB87C6” because a file with the same name already exists." UserInfo={NSFilePath=/Users/jal/Library/Developer/CoreSimulator/Devices/E8A35774-C9B7-42F0-93F1-8103FBBC7118/data/Containers/Data/Application/35B0B3BF-D502-4BA0-A991-D07568AB87C6/Documents, NSUnderlyingError=0x7fa88bd14410 {Error Domain=NSPOSIXErrorDomain Code=17 "File exists"}}

NSError

In order to see the different types of errors a function can throw, you would have to examine the error for information to determine the type of error thrown, and how to handle each error. In the case of NSError this would be its domain, code, and description.

In this particular case, a directory already exists at that path, so the file manager cannot create a new directory. An example of another reason why this operation could fail would be if the file manager did not have write access. That would be error code 256.

How Can I Find a List of All Exceptions That a Given Library Function Throws in Python?

To amplify Messa, catch what you expect are failure modes that you know how to recover from. Ian Bicking wrote an article that addresses some of the overarching principles as does Eli Bendersky's note.

The problem with the sample code is that it is not handling errors, just prettifying them and discarding them. Your code does not "know" what to do with a NameError and there isn't much it should do other than pass it up, look at Bicking's re-raise if you feel you must add detail.

IOError and OSError are reasonably "expectable" for a shutil.move but not necessarily handleable. And the caller of your function wanted it to move a file and may itself break if that "contract" that Eli writes of is broken.

Catch what you can fix, adorn and re-raise what you expect but can't fix, and let the caller deal with what you didn't expect, even if the code that "deals" is seven levels up the stack in main.

How to list all exceptions a function could raise in Python 3?

You can't get reliable results for some (if not most) functions. Some examples:

  • functions that execute arbitrary code (e.g. exec(')(rorrEeulaV esiar'[::-1]) raises ValueError)

  • functions that aren't written in Python

  • functions that call other functions that can propagate errors to the caller

  • functions re-raising active exceptions in the except: block

Unfortunately, this list is incomplete.

E.g. os.makedirs is written in Python and you can see its source:

...
try:
mkdir(name, mode)
except OSError as e:
if not exist_ok or e.errno != errno.EEXIST or not path.isdir(name):
raise

Bare raise re-raises the last active exception (OSError or one of its subclasses). Here's the class hierarchy for OSError:

+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError

To get the exact exception types you'll need to look into mkdir, functions it calls, functions those functions call etc.

So, getting possible exceptions without running the function is very hard and you really should not do it.


However for simple cases like

raise Exception # without arguments
raise Exception('abc') # with arguments

a combination of ast module functionality and inspect.getclosurevars (to get exception classes, was introduced in Python 3.3) can produce quite accurate results:

from inspect import getclosurevars, getsource
from collections import ChainMap
from textwrap import dedent
import ast, os

class MyException(Exception):
pass

def g():
raise Exception

class A():
def method():
raise OSError

def f(x):
int()
A.method()
os.makedirs()
g()
raise MyException
raise ValueError('argument')

def get_exceptions(func, ids=set()):
try:
vars = ChainMap(*getclosurevars(func)[:3])
source = dedent(getsource(func))
except TypeError:
return

class _visitor(ast.NodeTransformer):
def __init__(self):
self.nodes = []
self.other = []

def visit_Raise(self, n):
self.nodes.append(n.exc)

def visit_Expr(self, n):
if not isinstance(n.value, ast.Call):
return
c, ob = n.value.func, None
if isinstance(c, ast.Attribute):
parts = []
while getattr(c, 'value', None):
parts.append(c.attr)
c = c.value
if c.id in vars:
ob = vars[c.id]
for name in reversed(parts):
ob = getattr(ob, name)

elif isinstance(c, ast.Name):
if c.id in vars:
ob = vars[c.id]

if ob is not None and id(ob) not in ids:
self.other.append(ob)
ids.add(id(ob))

v = _visitor()
v.visit(ast.parse(source))
for n in v.nodes:
if isinstance(n, (ast.Call, ast.Name)):
name = n.id if isinstance(n, ast.Name) else n.func.id
if name in vars:
yield vars[name]

for o in v.other:
yield from get_exceptions(o)

for e in get_exceptions(f):
print(e)

prints

<class '__main__.MyException'>
<class 'ValueError'>
<class 'OSError'>
<class 'Exception'>

Keep in mind that this code only works for functions written in Python.

How to find the kind of errors a method may throw and catch them in Swift

NSError automatically bridges to ErrorType where the domain becomes the type (e.g. NSCocoaErrorDomain becomes CocoaError) and the error code becomes the value (NSFileReadNoSuchFileError becomes .fileNoSuchFile)

import Foundation

let docsPath = "/file/not/found"
let fileManager = FileManager()

do {
let docsArray = try fileManager.contentsOfDirectoryAtPath(docsPath)
} catch CocoaError.fileNoSuchFile {
print("No such file")
} catch let error {
// other errors
print(error.localizedDescription)
}

As for knowing which error can be returned by a specific call, only the documentation can help. Almost all Foundation errors are part of the CocoaError domain and can be found in FoundationErrors.h (though there are some rare bugs where Foundation can also sometimes return POSIX errors under NSPOSIXErrorDomain) but these ones might not have been fully bridged so you will have to fall back on managing them at the NSError level.

More information can be found in « Using Swift with Cocoa and Objective-C (Swift 2.2) »

How can I know which exceptions might be thrown from a method call?

I guess a solution could be only imprecise because of lack of static typing rules.

I'm not aware of some tool that checks exceptions, but you could come up with your own tool matching your needs (a good chance to play a little with static analysis).

As a first attempt, you could write a function that builds an AST, finds all Raise nodes, and then tries to figure out common patterns of raising exceptions (e. g. calling a constructor directly)

Let x be the following program:

x = '''\
if f(x):
raise IOError(errno.ENOENT, 'not found')
else:
e = g(x)
raise e
'''

Build the AST using the compiler package:

tree = compiler.parse(x)

Then define a Raise visitor class:

class RaiseVisitor(object):
def __init__(self):
self.nodes = []
def visitRaise(self, n):
self.nodes.append(n)

And walk the AST collecting Raise nodes:

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
Raise(
CallFunc(
Name('IOError'),
[Getattr(Name('errno'), 'ENOENT'), Const('not found')],
None, None),
None, None),
Raise(Name('e'), None, None),
]

You may continue by resolving symbols using compiler symbol tables, analyzing data dependencies, etc. Or you may just deduce, that CallFunc(Name('IOError'), ...) "should definitely mean raising IOError", which is quite OK for quick practical results :)

How to test the type of a thrown exception in Jest

In Jest you have to pass a function into expect(function).toThrow(<blank or type of error>).

Example:

test("Test description", () => {
const t = () => {
throw new TypeError();
};
expect(t).toThrow(TypeError);
});

Or if you also want to check for error message:

test("Test description", () => {
const t = () => {
throw new TypeError("UNKNOWN ERROR");
};
expect(t).toThrow(TypeError);
expect(t).toThrow("UNKNOWN ERROR");
});

If you need to test an existing function whether it throws with a set of arguments, you have to wrap it inside an anonymous function in expect().

Example:

test("Test description", () => {
expect(() => {http.get(yourUrl, yourCallbackFn)}).toThrow(TypeError);
});


Related Topics



Leave a reply



Submit