Skip to content

Commit

Permalink
Merge "Tests: improve assertJsonEqual diagnostic message"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Feb 20, 2017
2 parents c746974 + 9d636ba commit 384e022
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 32 deletions.
43 changes: 28 additions & 15 deletions nova/test.py
Expand Up @@ -30,6 +30,7 @@
import inspect
import mock
import os
import pprint

import fixtures
from oslo_cache import core as cache
Expand Down Expand Up @@ -358,7 +359,7 @@ def start_service(self, name, host=None, **kwargs):

return svc.service

def assertJsonEqual(self, expected, observed):
def assertJsonEqual(self, expected, observed, message=''):
"""Asserts that 2 complex data structures are json equivalent.
We use data structures which serialize down to json throughout
Expand Down Expand Up @@ -388,37 +389,49 @@ def sort_key(x):
return sorted(items)
return x

def inner(expected, observed):
def inner(expected, observed, path='root'):
if isinstance(expected, dict) and isinstance(observed, dict):
self.assertEqual(len(expected), len(observed))
self.assertEqual(
len(expected), len(observed),
'path: %s. Dict lengths are not equal' % path)
expected_keys = sorted(expected)
observed_keys = sorted(observed)
self.assertEqual(expected_keys, observed_keys)

self.assertEqual(
expected_keys, observed_keys,
'path: %s. Dict keys are not equal' % path)
for key in list(six.iterkeys(expected)):
inner(expected[key], observed[key])
inner(expected[key], observed[key], path + '.%s' % key)
elif (isinstance(expected, (list, tuple, set)) and
isinstance(observed, (list, tuple, set))):
self.assertEqual(len(expected), len(observed))
self.assertEqual(
len(expected), len(observed),
'path: %s. List lengths are not equal' % path)

expected_values_iter = iter(sorted(expected, key=sort_key))
observed_values_iter = iter(sorted(observed, key=sort_key))

for i in range(len(expected)):
inner(next(expected_values_iter),
next(observed_values_iter))
next(observed_values_iter), path + '[%s]' % i)
else:
self.assertEqual(expected, observed)
self.assertEqual(expected, observed, 'path: %s' % path)

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)
difference = e.mismatch.describe()
if message:
message = 'message: %s\n' % message
msg = "\nexpected:\n%s\nactual:\n%s\ndifference:\n%s\n%s" % (
pprint.pformat(expected),
pprint.pformat(observed),
difference,
message)
error = AssertionError(msg)
error.expected = expected
error.observed = observed
error.difference = difference
raise error

def assertPublicAPISignatures(self, baseinst, inst):
def get_public_apis(inst):
Expand Down
116 changes: 99 additions & 17 deletions nova/tests/unit/test_test.py
Expand Up @@ -53,7 +53,7 @@ def __getattribute__(*args):


class JsonTestCase(test.NoDBTestCase):
def test_json_equal(self):
def test_compare_dict_string(self):
expected = {
"employees": [
{"firstName": "Anna", "lastName": "Smith"},
Expand All @@ -62,7 +62,7 @@ def test_json_equal(self):
],
"locations": set(['Boston', 'Mumbai', 'Beijing', 'Perth'])
}
observed = """{
actual = """{
"employees": [
{
"lastName": "Doe",
Expand All @@ -84,68 +84,150 @@ def test_json_equal(self):
"Beijing"
]
}"""
self.assertJsonEqual(expected, observed)
self.assertJsonEqual(expected, actual)

def test_json_equal_fail_on_length(self):
def test_fail_on_list_length(self):
expected = {
'top': {
'l1': {
'l2': ['a', 'b', 'c']
}
}
}
observed = {
actual = {
'top': {
'l1': {
'l2': ['c', 'a', 'b', 'd']
}
}
}
try:
self.assertJsonEqual(expected, observed)
self.assertJsonEqual(expected, actual)
except Exception as e:
# error reported is going to be a cryptic length failure
# on the level2 structure.
self.assertEqual(e.mismatch.describe(), "3 != 4")
self.assertEqual(
"3 != 4: path: root.top.l1.l2. List lengths are not equal",
e.difference)
self.assertIn(
"Matchee: {'top': {'l1': {'l2': ['c', 'a', 'b', 'd']}}}",
"actual:\n{'top': {'l1': {'l2': ['c', 'a', 'b', 'd']}}}",
six.text_type(e))
self.assertIn(
"Matcher: {'top': {'l1': {'l2': ['a', 'b', 'c']}}}",
"expected:\n{'top': {'l1': {'l2': ['a', 'b', 'c']}}}",
six.text_type(e))
else:
self.fail("This should have raised a mismatch exception")

def test_json_equal_fail_on_inner(self):
def test_fail_on_dict_length(self):
expected = {
'top': {
'l1': {
'l2': {'a': 1, 'b': 2, 'c': 3}
}
}
}
actual = {
'top': {
'l1': {
'l2': {'a': 1, 'b': 2}
}
}
}
try:
self.assertJsonEqual(expected, actual)
except Exception as e:
self.assertEqual(
"3 != 2: path: root.top.l1.l2. Dict lengths are not equal",
e.difference)
else:
self.fail("This should have raised a mismatch exception")

def test_fail_on_dict_keys(self):
expected = {
'top': {
'l1': {
'l2': {'a': 1, 'b': 2, 'c': 3}
}
}
}
actual = {
'top': {
'l1': {
'l2': {'a': 1, 'b': 2, 'd': 3}
}
}
}
try:
self.assertJsonEqual(expected, actual)
except Exception as e:
self.assertIn(
"path: root.top.l1.l2. Dict keys are not equal",
e.difference)
else:
self.fail("This should have raised a mismatch exception")

def test_fail_on_list_value(self):
expected = {
'top': {
'l1': {
'l2': ['a', 'b', 'c']
}
}
}
observed = {
actual = {
'top': {
'l1': {
'l2': ['c', 'a', 'd']
}
}
}
try:
self.assertJsonEqual(expected, observed)
self.assertJsonEqual(expected, actual)
except Exception as e:
# error reported is going to be a cryptic length failure
# on the level2 structure.
self.assertEqual(e.mismatch.describe(), "'b' != 'c'")
self.assertEqual(
"'b' != 'c': path: root.top.l1.l2[1]",
e.difference)
self.assertIn(
"Matchee: {'top': {'l1': {'l2': ['c', 'a', 'd']}}}",
"actual:\n{'top': {'l1': {'l2': ['c', 'a', 'd']}}}",
six.text_type(e))
self.assertIn(
"Matcher: {'top': {'l1': {'l2': ['a', 'b', 'c']}}}",
"expected:\n{'top': {'l1': {'l2': ['a', 'b', 'c']}}}",
six.text_type(e))
else:
self.fail("This should have raised a mismatch exception")

def test_fail_on_dict_value(self):
expected = {
'top': {
'l1': {
'l2': {'a': 1, 'b': 2, 'c': 3}
}
}
}
actual = {
'top': {
'l1': {
'l2': {'a': 1, 'b': 2, 'c': 4}
}
}
}
try:
self.assertJsonEqual(expected, actual, 'test message')
except Exception as e:
self.assertEqual(
"3 != 4: path: root.top.l1.l2.c", e.difference)
self.assertIn("actual:\n{'top': {'l1': {'l2': {", six.text_type(e))
self.assertIn(
"expected:\n{'top': {'l1': {'l2': {", six.text_type(e))
self.assertIn(
"message: test message\n", six.text_type(e))
else:
self.fail("This should have raised a mismatch exception")

def test_compare_scalars(self):
with self.assertRaisesRegex(AssertionError, 'True != False'):
self.assertJsonEqual(True, False)


class BadLogTestCase(test.NoDBTestCase):
"""Make sure a mis-formatted debug log will get caught."""
Expand Down

0 comments on commit 384e022

Please sign in to comment.