test_compliance.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import os
  2. from pprint import pformat
  3. from . import OrderedDict
  4. from . import json
  5. import pytest
  6. from jmespath.visitor import Options
  7. TEST_DIR = os.path.dirname(os.path.abspath(__file__))
  8. COMPLIANCE_DIR = os.path.join(TEST_DIR, 'compliance')
  9. LEGACY_DIR = os.path.join(TEST_DIR, 'legacy')
  10. NOT_SPECIFIED = object()
  11. OPTIONS = Options(dict_cls=OrderedDict)
  12. def _compliance_tests(requested_test_type):
  13. for full_path in _walk_files():
  14. if full_path.endswith('.json'):
  15. for given, test_type, test_data in load_cases(full_path):
  16. t = test_data
  17. # Benchmark tests aren't run as part of the normal
  18. # test suite, so we only care about 'result' and
  19. # 'error' test_types.
  20. if test_type == 'result' and test_type == requested_test_type:
  21. yield (given, t['expression'],
  22. t['result'], os.path.basename(full_path))
  23. elif test_type == 'error' and test_type == requested_test_type:
  24. yield (given, t['expression'],
  25. t['error'], os.path.basename(full_path))
  26. def _walk_files():
  27. # Check for a shortcut when running the tests interactively.
  28. # If a JMESPATH_TEST is defined, that file is used as the
  29. # only test to run. Useful when doing feature development.
  30. single_file = os.environ.get('JMESPATH_TEST')
  31. if single_file is not None:
  32. yield os.path.abspath(single_file)
  33. else:
  34. for root, dirnames, filenames in os.walk(TEST_DIR):
  35. for filename in filenames:
  36. yield os.path.join(root, filename)
  37. for root, dirnames, filenames in os.walk(LEGACY_DIR):
  38. for filename in filenames:
  39. yield os.path.join(root, filename)
  40. def load_cases(full_path):
  41. all_test_data = json.load(open(full_path), object_pairs_hook=OrderedDict)
  42. for test_data in all_test_data:
  43. given = test_data['given']
  44. for case in test_data['cases']:
  45. if 'result' in case:
  46. test_type = 'result'
  47. elif 'error' in case:
  48. test_type = 'error'
  49. elif 'bench' in case:
  50. test_type = 'bench'
  51. else:
  52. raise RuntimeError("Unknown test type: %s" % json.dumps(case))
  53. yield (given, test_type, case)
  54. @pytest.mark.parametrize(
  55. 'given, expression, expected, filename',
  56. _compliance_tests('result')
  57. )
  58. def test_expression(given, expression, expected, filename):
  59. import jmespath.parser
  60. try:
  61. parsed = jmespath.compile(expression)
  62. except ValueError as e:
  63. raise AssertionError(
  64. 'jmespath expression failed to compile: "%s", error: %s"' %
  65. (expression, e))
  66. actual = parsed.search(given, options=OPTIONS)
  67. expected_repr = json.dumps(expected, indent=4)
  68. actual_repr = json.dumps(actual, indent=4)
  69. error_msg = ("\n\n (%s) The expression '%s' was suppose to give:\n%s\n"
  70. "Instead it matched:\n%s\nparsed as:\n%s\ngiven:\n%s" % (
  71. filename, expression, expected_repr,
  72. actual_repr, pformat(parsed.parsed),
  73. json.dumps(given, indent=4)))
  74. error_msg = error_msg.replace(r'\n', '\n')
  75. assert actual == expected, error_msg
  76. @pytest.mark.parametrize(
  77. 'given, expression, error, filename',
  78. _compliance_tests('error')
  79. )
  80. def test_error_expression(given, expression, error, filename):
  81. import jmespath.parser
  82. if error not in ('syntax', 'invalid-type',
  83. 'unknown-function', 'invalid-arity', 'invalid-value'):
  84. raise RuntimeError("Unknown error type '%s'" % error)
  85. try:
  86. parsed = jmespath.compile(expression)
  87. parsed.search(given)
  88. except ValueError:
  89. # Test passes, it raised a parse error as expected.
  90. pass
  91. except Exception as e:
  92. # Failure because an unexpected exception was raised.
  93. error_msg = ("\n\n (%s) The expression '%s' was suppose to be a "
  94. "syntax error, but it raised an unexpected error:\n\n%s" % (
  95. filename, expression, e))
  96. error_msg = error_msg.replace(r'\n', '\n')
  97. raise AssertionError(error_msg)
  98. else:
  99. error_msg = ("\n\n (%s) The expression '%s' was suppose to be a "
  100. "syntax error, but it successfully parsed as:\n\n%s" % (
  101. filename, expression, pformat(parsed.parsed)))
  102. error_msg = error_msg.replace(r'\n', '\n')
  103. raise AssertionError(error_msg)