From 09e21b44879ea05365d56feca6ce35a8c1753a63 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Wed, 28 Jun 2017 15:04:00 -0300 Subject: topotest: implement json_cmp function Implemented a JSON compare function that tells you when a specific subset of items exist or not inside a JSON dataset. More details can be found in the function docstring or in the test file lib/test_json.py. --- tests/topotests/lib/test/test_json.py | 253 ++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100755 tests/topotests/lib/test/test_json.py (limited to 'tests/topotests/lib/test/test_json.py') diff --git a/tests/topotests/lib/test/test_json.py b/tests/topotests/lib/test/test_json.py new file mode 100755 index 0000000000..87bf48dda2 --- /dev/null +++ b/tests/topotests/lib/test/test_json.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python + +# +# test_json.py +# Tests for library function: json_cmp(). +# +# Copyright (c) 2017 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +Tests for the json_cmp() function. +""" + +import os +import sys +import pytest + +# Save the Current Working Directory to find lib files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, '../../')) + +# pylint: disable=C0413 +from lib.topotest import json_cmp + +def test_json_intersect_true(): + "Test simple correct JSON intersections" + + dcomplete = { + 'i1': 'item1', + 'i2': 'item2', + 'i3': 'item3', + 'i100': 'item4', + } + + dsub1 = { + 'i1': 'item1', + 'i3': 'item3', + } + dsub2 = { + 'i1': 'item1', + 'i2': 'item2', + } + dsub3 = { + 'i100': 'item4', + 'i2': 'item2', + } + dsub4 = { + 'i50': None, + 'i100': 'item4', + } + + assert json_cmp(dcomplete, dsub1) is None + assert json_cmp(dcomplete, dsub2) is None + assert json_cmp(dcomplete, dsub3) is None + assert json_cmp(dcomplete, dsub4) is None + +def test_json_intersect_false(): + "Test simple incorrect JSON intersections" + + dcomplete = { + 'i1': 'item1', + 'i2': 'item2', + 'i3': 'item3', + 'i100': 'item4', + } + + # Incorrect value for 'i1' + dsub1 = { + 'i1': 'item3', + 'i3': 'item3', + } + # Non-existing key 'i5' + dsub2 = { + 'i1': 'item1', + 'i5': 'item2', + } + # Key should not exist + dsub3 = { + 'i100': None, + } + + assert json_cmp(dcomplete, dsub1) is not None + assert json_cmp(dcomplete, dsub2) is not None + assert json_cmp(dcomplete, dsub3) is not None + +def test_json_intersect_multilevel_true(): + "Test multi level correct JSON intersections" + + dcomplete = { + 'i1': 'item1', + 'i2': 'item2', + 'i3': { + 'i100': 'item100', + }, + 'i4': { + 'i41': { + 'i411': 'item411', + }, + 'i42': { + 'i421': 'item421', + 'i422': 'item422', + } + } + } + + dsub1 = { + 'i1': 'item1', + 'i3': { + 'i100': 'item100', + }, + 'i10': None, + } + dsub2 = { + 'i1': 'item1', + 'i2': 'item2', + 'i3': {}, + } + dsub3 = { + 'i2': 'item2', + 'i4': { + 'i41': { + 'i411': 'item411', + }, + 'i42': { + 'i422': 'item422', + 'i450': None, + } + } + } + dsub4 = { + 'i2': 'item2', + 'i4': { + 'i41': {}, + 'i42': { + 'i450': None, + } + } + } + dsub5 = { + 'i2': 'item2', + 'i3': { + 'i100': 'item100', + }, + 'i4': { + 'i42': { + 'i450': None, + } + } + } + + assert json_cmp(dcomplete, dsub1) is None + assert json_cmp(dcomplete, dsub2) is None + assert json_cmp(dcomplete, dsub3) is None + assert json_cmp(dcomplete, dsub4) is None + assert json_cmp(dcomplete, dsub5) is None + +def test_json_intersect_multilevel_false(): + "Test multi level incorrect JSON intersections" + + dcomplete = { + 'i1': 'item1', + 'i2': 'item2', + 'i3': { + 'i100': 'item100', + }, + 'i4': { + 'i41': { + 'i411': 'item411', + }, + 'i42': { + 'i421': 'item421', + 'i422': 'item422', + } + } + } + + # Incorrect sub-level value + dsub1 = { + 'i1': 'item1', + 'i3': { + 'i100': 'item00', + }, + 'i10': None, + } + # Inexistent sub-level + dsub2 = { + 'i1': 'item1', + 'i2': 'item2', + 'i3': None, + } + # Inexistent sub-level value + dsub3 = { + 'i1': 'item1', + 'i3': { + 'i100': None, + }, + } + # Inexistent sub-sub-level value + dsub4 = { + 'i4': { + 'i41': { + 'i412': 'item412', + }, + 'i42': { + 'i421': 'item421', + } + } + } + # Invalid sub-sub-level value + dsub5 = { + 'i4': { + 'i41': { + 'i411': 'item411', + }, + 'i42': { + 'i421': 'item420000', + } + } + } + # sub-sub-level should be value + dsub6 = { + 'i4': { + 'i41': { + 'i411': 'item411', + }, + 'i42': 'foobar', + } + } + + assert json_cmp(dcomplete, dsub1) is not None + assert json_cmp(dcomplete, dsub2) is not None + assert json_cmp(dcomplete, dsub3) is not None + assert json_cmp(dcomplete, dsub4) is not None + assert json_cmp(dcomplete, dsub5) is not None + assert json_cmp(dcomplete, dsub6) is not None + +if __name__ == '__main__': + sys.exit(pytest.main()) -- cgit v1.2.3 From dc0d3fc53f86310cb5789dedf647f981975e94fa Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Wed, 5 Jul 2017 13:46:28 -0300 Subject: topotest: add JSON list comparation support Add missing list support for json_cmp(). The missing support was noticed while writing the BGP ECMP topology test. --- tests/topotests/lib/test/test_json.py | 80 +++++++++++++++++++++++++++++++++++ tests/topotests/lib/topotest.py | 41 ++++++++++++++++++ 2 files changed, 121 insertions(+) (limited to 'tests/topotests/lib/test/test_json.py') diff --git a/tests/topotests/lib/test/test_json.py b/tests/topotests/lib/test/test_json.py index 87bf48dda2..e397194503 100755 --- a/tests/topotests/lib/test/test_json.py +++ b/tests/topotests/lib/test/test_json.py @@ -249,5 +249,85 @@ def test_json_intersect_multilevel_false(): assert json_cmp(dcomplete, dsub5) is not None assert json_cmp(dcomplete, dsub6) is not None +def test_json_with_list_sucess(): + "Test successful json comparisons that have lists." + + dcomplete = { + 'list': [ + { + 'i1': 'item 1', + 'i2': 'item 2', + }, + { + 'i10': 'item 10', + }, + ], + 'i100': 'item 100', + } + + # Test list type + dsub1 = { + 'list': [], + } + # Test list correct list items + dsub2 = { + 'list': [ + { + 'i1': 'item 1', + }, + ], + 'i100': 'item 100', + } + # Test list correct list size + dsub3 = { + 'list': [ + {}, {}, + ], + } + + assert json_cmp(dcomplete, dsub1) is None + assert json_cmp(dcomplete, dsub2) is None + assert json_cmp(dcomplete, dsub3) is None + +def test_json_with_list_failure(): + "Test failed json comparisons that have lists." + + dcomplete = { + 'list': [ + { + 'i1': 'item 1', + 'i2': 'item 2', + }, + { + 'i10': 'item 10', + }, + ], + 'i100': 'item 100', + } + + # Test list type + dsub1 = { + 'list': {}, + } + # Test list incorrect list items + dsub2 = { + 'list': [ + { + 'i1': 'item 2', + }, + ], + 'i100': 'item 100', + } + # Test list correct list size + dsub3 = { + 'list': [ + {}, {}, {}, + ], + } + + assert json_cmp(dcomplete, dsub1) is not None + assert json_cmp(dcomplete, dsub2) is not None + assert json_cmp(dcomplete, dsub3) is not None + if __name__ == '__main__': sys.exit(pytest.main()) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 9ed7c198d1..fa065ad178 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -91,9 +91,50 @@ def json_cmp(d1, d2, reason=False): continue # If nd1 key is a dict, we have to recurse in it later. if isinstance(nd2[key], type({})): + if not isinstance(nd1[key], type({})): + result.add_error( + '{}["{}"] has different type than expected '.format(parent, key) + + '(have {}, expected {})'.format(type(nd1[key]), type(nd2[key]))) + continue nparent = '{}["{}"]'.format(parent, key) squeue.append((nd1[key], nd2[key], nparent)) continue + # Check list items + if isinstance(nd2[key], type([])): + if not isinstance(nd1[key], type([])): + result.add_error( + '{}["{}"] has different type than expected '.format(parent, key) + + '(have {}, expected {})'.format(type(nd1[key]), type(nd2[key]))) + continue + # Check list size + if len(nd2[key]) > len(nd1[key]): + result.add_error( + '{}["{}"] too few items '.format(parent, key) + + '(have ({}) "{}", expected ({}) "{}")'.format( + len(nd1[key]), str(nd1[key]), len(nd2[key]), str(nd2[key]))) + continue + + # List all unmatched items errors + unmatched = [] + for expected in nd2[key]: + matched = False + for value in nd1[key]: + if json_cmp({'json': value}, {'json': expected}) is None: + matched = True + break + + if matched: + break + if not matched: + unmatched.append(expected) + + # If there are unmatched items, error out. + if unmatched: + result.add_error( + '{}["{}"] value is different (have "{}", expected "{}")'.format( + parent, key, str(nd1[key]), str(nd2[key]))) + continue + # Compare JSON values if nd1[key] != nd2[key]: result.add_error( -- cgit v1.2.3 From 3e379f6e49e1d4a93f34270a52881e82ea8e7173 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Thu, 26 Jul 2018 16:34:15 -0300 Subject: topotests: fix json_cmp wrong list handling Don't quit on the first match. While here add some more `json_cmp` tests to make sure this doesn't happen again. Signed-off-by: Rafael Zalamena --- tests/topotests/lib/test/test_json.py | 123 ++++++++++++++++++++++++++++++++++ tests/topotests/lib/topotest.py | 2 - 2 files changed, 123 insertions(+), 2 deletions(-) (limited to 'tests/topotests/lib/test/test_json.py') diff --git a/tests/topotests/lib/test/test_json.py b/tests/topotests/lib/test/test_json.py index e397194503..3927ba095d 100755 --- a/tests/topotests/lib/test/test_json.py +++ b/tests/topotests/lib/test/test_json.py @@ -329,5 +329,128 @@ def test_json_with_list_failure(): assert json_cmp(dcomplete, dsub2) is not None assert json_cmp(dcomplete, dsub3) is not None + +def test_json_list_start_success(): + "Test JSON encoded data that starts with a list that should succeed." + + dcomplete = [ + { + "id": 100, + "value": "abc", + }, + { + "id": 200, + "value": "abcd", + }, + { + "id": 300, + "value": "abcde", + }, + ] + + dsub1 = [ + { + "id": 100, + "value": "abc", + } + ] + + dsub2 = [ + { + "id": 100, + "value": "abc", + }, + { + "id": 200, + "value": "abcd", + } + ] + + dsub3 = [ + { + "id": 300, + "value": "abcde", + } + ] + + dsub4 = [ + ] + + dsub5 = [ + { + "id": 100, + } + ] + + assert json_cmp(dcomplete, dsub1) is None + assert json_cmp(dcomplete, dsub2) is None + assert json_cmp(dcomplete, dsub3) is None + assert json_cmp(dcomplete, dsub4) is None + assert json_cmp(dcomplete, dsub5) is None + + +def test_json_list_start_failure(): + "Test JSON encoded data that starts with a list that should fail." + + dcomplete = [ + { + "id": 100, + "value": "abc" + }, + { + "id": 200, + "value": "abcd" + }, + { + "id": 300, + "value": "abcde" + }, + ] + + dsub1 = [ + { + "id": 100, + "value": "abcd", + } + ] + + dsub2 = [ + { + "id": 100, + "value": "abc", + }, + { + "id": 200, + "value": "abc", + } + ] + + dsub3 = [ + { + "id": 100, + "value": "abc", + }, + { + "id": 350, + "value": "abcde", + } + ] + + dsub4 = [ + { + "value": "abcx", + }, + { + "id": 300, + "value": "abcde", + } + ] + + assert json_cmp(dcomplete, dsub1) is not None + assert json_cmp(dcomplete, dsub2) is not None + assert json_cmp(dcomplete, dsub3) is not None + assert json_cmp(dcomplete, dsub4) is not None + + if __name__ == '__main__': sys.exit(pytest.main()) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 7f66098fc7..1cbde825ac 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -118,8 +118,6 @@ def _json_list_cmp(list1, list2, parent, result): matched = True break - if matched: - break if not matched: unmatched.append(expected) -- cgit v1.2.3