Skip to content

TST: better assertion messages on test failures (GH4397) #4430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 1, 2013
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 58 additions & 32 deletions pandas/util/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def set_trace():

#------------------------------------------------------------------------------
# contextmanager to ensure the file cleanup


@contextmanager
def ensure_clean(filename=None):
# if we are not passed a filename, generate a temporary
Expand All @@ -114,6 +116,8 @@ def get_data_path(f=''):

#------------------------------------------------------------------------------
# Comparators


def equalContents(arr1, arr2):
"""Checks if the set of unique elements of arr1 and arr2 are equivalent.
"""
Expand All @@ -127,6 +131,20 @@ def assert_isinstance(obj, class_type_or_tuple):
type(obj), class_type_or_tuple))


def assert_index_equal(left, right):
assert left.equals(
right), "[index] left [{0}], right [{0}]".format(left, right)


def assert_attr_equal(attr, left, right):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is as useful as what we had previously. It needs to raise when one of the components does not have an attribute (e.g., it's the wrong type altogether or something weird has happened). Further, if you do hasattr or getattr, it's not exactly equivalent to trying to get the attribute and defaulting to None otherwise. (and also I think getattr ignores any exception that occurs when trying to get it). So, I think it's better to add an assert_equal that takes an argument which explains. e.g.

assert_equal(left.column.dtype, right.column.dtype, "column dtypes did not match')

Where assert_equal is:

def assert_equal(actual, expected, msg=""):
     assert expected == actual, "%s: %r != %r" % (msg, actual, expected)

Plus, this means that the Exception that will bubbles up if something goes wrong will not be on assert_attr_equal but will instead be on the line assert_equal(left.column.dtype, right.column.dtype, msg) and you'll get the nice error message about not having an attribute and you'll see it's on the column...much easier to debug.

To illustrate the difference, check out this small example:

class SomeClass(object):
    @property
    def failing_property(self):
        return "wefawef".unreal_method(["a"])

obj = SomeClass()
print getattr(obj, "failing_property", None) # no error
print obj.failing_property # fails appropriately

And if you just change the getattr(left, attr, None) to getattr(left, attr), you still lose the nicer error output because the actual error line will read getattr(left, attr) rather than left.column.dtype

left_attr = getattr(left, attr, None)
right_attr = getattr(right, attr, None)
assert left_attr == right_attr, "[{0}] left [{1}], right [{2}]".format(
attr,
left_attr,
right_attr)


def isiterable(obj):
return hasattr(obj, '__iter__')

Expand All @@ -137,7 +155,7 @@ def assert_isinstance(obj, class_type_or_tuple):
"Expected object to be of type %r, found %r instead" % (type(obj), class_type_or_tuple))


def assert_almost_equal(a, b, check_less_precise = False):
def assert_almost_equal(a, b, check_less_precise=False):
if isinstance(a, dict) or isinstance(b, dict):
return assert_dict_equal(a, b)

Expand Down Expand Up @@ -212,18 +230,18 @@ def assert_series_equal(left, right, check_dtype=True,
assert_isinstance(left, type(right))
assert_almost_equal(left.values, right.values, check_less_precise)
if check_dtype:
assert(left.dtype == right.dtype)
assert_attr_equal('dtype', left, right)
if check_less_precise:
assert_almost_equal(left.index.values, right.index.values, check_less_precise)
assert_almost_equal(
left.index.values, right.index.values, check_less_precise)
else:
assert(left.index.equals(right.index))
assert_index_equal(left.index, right.index)
if check_index_type:
assert_isinstance(left.index, type(right.index))
assert(left.index.dtype == right.index.dtype)
assert(left.index.inferred_type == right.index.inferred_type)
assert_attr_equal('dtype', left.index, right.index)
assert_attr_equal('inferred_type', left.index, right.index)
if check_index_freq:
assert(getattr(left, 'freqstr', None) ==
getattr(right, 'freqstr', None))
assert_attr_equal('freqstr', left.index, right.index)


def assert_frame_equal(left, right, check_dtype=True,
Expand All @@ -238,11 +256,11 @@ def assert_frame_equal(left, right, check_dtype=True,
assert_isinstance(right, DataFrame)

if check_less_precise:
assert_almost_equal(left.columns,right.columns)
assert_almost_equal(left.index,right.index)
assert_almost_equal(left.columns, right.columns)
assert_almost_equal(left.index, right.index)
else:
assert(left.columns.equals(right.columns))
assert(left.index.equals(right.index))
assert_index_equal(left.columns, right.columns)
assert_index_equal(left.index, right.index)

for i, col in enumerate(left.columns):
assert(col in right)
Expand All @@ -255,15 +273,15 @@ def assert_frame_equal(left, right, check_dtype=True,

if check_index_type:
assert_isinstance(left.index, type(right.index))
assert(left.index.dtype == right.index.dtype)
assert(left.index.inferred_type == right.index.inferred_type)
assert_attr_equal('dtype', left.index, right.index)
assert_attr_equal('inferred_type', left.index, right.index)
if check_column_type:
assert_isinstance(left.columns, type(right.columns))
assert(left.columns.dtype == right.columns.dtype)
assert(left.columns.inferred_type == right.columns.inferred_type)
assert_attr_equal('dtype', left.columns, right.columns)
assert_attr_equal('inferred_type', left.columns, right.columns)
if check_names:
assert(left.index.names == right.index.names)
assert(left.columns.names == right.columns.names)
assert_attr_equal('names', left.index, right.index)
assert_attr_equal('names', left.columns, right.columns)


def assert_panel_equal(left, right,
Expand All @@ -272,28 +290,30 @@ def assert_panel_equal(left, right,
if check_panel_type:
assert_isinstance(left, type(right))

assert(left.items.equals(right.items))
assert(left.major_axis.equals(right.major_axis))
assert(left.minor_axis.equals(right.minor_axis))
for axis in ['items', 'major_axis', 'minor_axis']:
assert_index_equal(
getattr(left, axis, None), getattr(right, axis, None))

for col, series in compat.iteritems(left):
assert(col in right)
assert_frame_equal(series, right[col], check_less_precise=check_less_precise, check_names=False) # TODO strangely check_names fails in py3 ?
# TODO strangely check_names fails in py3 ?
assert_frame_equal(
series, right[col], check_less_precise=check_less_precise, check_names=False)

for col in right:
assert(col in left)


def assert_panel4d_equal(left, right,
check_less_precise=False):
assert(left.labels.equals(right.labels))
assert(left.items.equals(right.items))
assert(left.major_axis.equals(right.major_axis))
assert(left.minor_axis.equals(right.minor_axis))
for axis in ['labels', 'items', 'major_axis', 'minor_axis']:
assert_index_equal(
getattr(left, axis, None), getattr(right, axis, None))

for col, series in compat.iteritems(left):
assert(col in right)
assert_panel_equal(series, right[col], check_less_precise=check_less_precise)
assert_panel_equal(
series, right[col], check_less_precise=check_less_precise)

for col in right:
assert(col in left)
Expand Down Expand Up @@ -487,8 +507,8 @@ def makeCustomIndex(nentries, nlevels, prefix='#', names=False, ndupe_l=None,
for i in range(nlevels):
def keyfunc(x):
import re
numeric_tuple = re.sub("[^\d_]_?","",x).split("_")
return lmap(int,numeric_tuple)
numeric_tuple = re.sub("[^\d_]_?", "", x).split("_")
return lmap(int, numeric_tuple)

# build a list of lists to create the index from
div_factor = nentries // ndupe_l[i] + 1
Expand Down Expand Up @@ -604,6 +624,7 @@ def add_nans_panel4d(panel4d):


class TestSubDict(dict):

def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)

Expand Down Expand Up @@ -677,6 +698,7 @@ def skip_if_no_package(*args, **kwargs):
# Additional tags decorators for nose
#


def optional_args(decorator):
"""allows a decorator to take optional positional and keyword arguments.
Assumes that taking a single, callable, positional argument means that
Expand Down Expand Up @@ -705,6 +727,7 @@ def dec(f):

_network_error_classes = IOError, HTTPException


@optional_args
def network(t, raise_on_error=_RAISE_NETWORK_ERROR_DEFAULT,
error_classes=_network_error_classes, num_runs=2):
Expand Down Expand Up @@ -796,9 +819,9 @@ def network_wrapper(*args, **kwargs):
raise
except Exception as e:
if runs < num_runs:
print("Failed: %r" % e)
print("Failed: %r" % e)
else:
raise
raise

runs += 1

Expand Down Expand Up @@ -913,6 +936,7 @@ def wrapper(*args, **kwargs):


class SimpleMock(object):

"""
Poor man's mocking object

Expand All @@ -926,6 +950,7 @@ class SimpleMock(object):
>>> a.attr1 == "fizz" and a.attr2 == "buzz"
True
"""

def __init__(self, obj, *args, **kwds):
assert(len(args) % 2 == 0)
attrs = kwds.get("attrs", {})
Expand Down Expand Up @@ -1010,7 +1035,8 @@ def assertRaisesRegexp(exception, regexp, callable, *args, **kwargs):
raise AssertionError('"%s" does not match "%s"' %
(expected_regexp.pattern, str(e)))
else:
# Apparently some exceptions don't have a __name__ attribute? Just aping unittest library here
# Apparently some exceptions don't have a __name__ attribute? Just
# aping unittest library here
name = getattr(exception, "__name__", str(exception))
raise AssertionError("{0} not raised".format(name))

Expand Down