return more details on assertJsonEqual fail

assertJsonEqual does a recursive traverse of a data structure, using
assertEqual strategically along the way. The problem with this is that
it can lead to really cryptic messages like

  Failed '3' != '4'

When some very deep list was the wrong number of elements. It doesn't
tell you which list was the wrong number of elements, and so it's
completely opaque to get to the bottom of. Especially if this doesn't
happen on every run, just some.

This creates a version which catches an inner match issue, and
constructs a new failure which includes information about the 2 data
structures being compared at the top level. This should help debugging
racey unit test in the gate like
test_describe_instances_with_filters_tags.

Related-Bug: #1479780

Change-Id: I6f6e961a3c63f9f86fe9b21ce6c16a2e634e9ce5
This commit is contained in:
Sean Dague 2015-07-30 07:03:14 -04:00
parent 50f53d2301
commit 8316582f8d

View File

@ -289,6 +289,21 @@ class TestCase(testtools.TestCase):
return svc.service return svc.service
def assertJsonEqual(self, expected, observed): def assertJsonEqual(self, expected, observed):
"""Asserts that 2 complex data structures are json equivalent.
We use data structures which serialize down to json throughout
the code, and often times we just need to know that these are
json equivalent. This means that list order is not important,
and should be sorted.
Because this is a recursive set of assertions, when failure
happens we want to expose both the local failure and the
global view of the 2 data structures being compared. So a
MismatchError which includes the inner failure as the
mismatch, and the passed in expected / observed as matchee /
matcher.
"""
if isinstance(expected, six.string_types): if isinstance(expected, six.string_types):
expected = jsonutils.loads(expected) expected = jsonutils.loads(expected)
if isinstance(observed, six.string_types): if isinstance(observed, six.string_types):
@ -325,7 +340,15 @@ class TestCase(testtools.TestCase):
else: else:
self.assertEqual(expected, observed) self.assertEqual(expected, observed)
inner(expected, observed) try:
inner(expected, observed)
except testtools.matchers.MismatchError as e:
inner_mismatch = e.mismatch
# inverting the observed / expected because testtools
# error messages assume expected is second. Possibly makes
# reading the error messages less confusing.
raise testtools.matchers.MismatchError(observed, expected,
inner_mismatch, verbose=True)
def assertPublicAPISignatures(self, baseinst, inst): def assertPublicAPISignatures(self, baseinst, inst):
def get_public_apis(inst): def get_public_apis(inst):