diff options
author | Paul McGuire <ptmcg@austin.rr.com> | 2019-09-27 06:02:38 -0500 |
---|---|---|
committer | Paul McGuire <ptmcg@austin.rr.com> | 2019-09-27 06:02:38 -0500 |
commit | 0029d02b37097f0bc9a893c14d2c3d286d55836f (patch) | |
tree | 79969f7506a62ff7f1bf80e9ff95c3b38eeee7b6 | |
parent | 3e5b2660c47d5ba50781bfbcdf291a1f829b9a88 (diff) | |
download | pyparsing-git-0029d02b37097f0bc9a893c14d2c3d286d55836f.tar.gz |
Merge unittest enhancements from branch
-rw-r--r-- | .idea/inspectionProfiles/Project_Default.xml | 11 | ||||
-rw-r--r-- | CHANGES | 8 | ||||
-rw-r--r-- | pyparsing.py | 142 | ||||
-rw-r--r-- | simple_unit_tests.py | 24 | ||||
-rw-r--r-- | tox.ini | 2 | ||||
-rw-r--r-- | unitTests.py | 418 |
6 files changed, 390 insertions, 215 deletions
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 4fa25d8..aea1ef6 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,12 +4,11 @@ <inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true"> <option name="ourVersions"> <value> - <list size="5"> - <item index="0" class="java.lang.String" itemvalue="2.7" /> - <item index="1" class="java.lang.String" itemvalue="3.4" /> - <item index="2" class="java.lang.String" itemvalue="3.5" /> - <item index="3" class="java.lang.String" itemvalue="3.6" /> - <item index="4" class="java.lang.String" itemvalue="3.7" /> + <list size="4"> + <item index="0" class="java.lang.String" itemvalue="3.5" /> + <item index="1" class="java.lang.String" itemvalue="3.6" /> + <item index="2" class="java.lang.String" itemvalue="3.7" /> + <item index="3" class="java.lang.String" itemvalue="3.8" /> </list> </value> </option> @@ -73,6 +73,14 @@ Version 3.0.0a1 pp.__diag__.enable_all_warnings() +- New namespace, assert methods and classes added to support writing unit tests. + - assertParseResultsEquals + - assertParseAndCheckList + - assertParseAndCheckDict + - assertRunTestResults + - assertRaisesParseException + - reset_pyparsing_context context manager, to restore pyparsing config settings + - Fixed handling of ParseSyntaxExceptions raised as part of Each expressions, when sub-expressions contain '-' backtrack suppression. As part of resolution to a question posted by John diff --git a/pyparsing.py b/pyparsing.py index 5f93a1f..91a313c 100644 --- a/pyparsing.py +++ b/pyparsing.py @@ -96,7 +96,7 @@ classes inherit from. Use the docstrings for examples of how to: """ __version__ = "3.0.0a1" -__versionTime__ = "25 Sep 2019 22:27 UTC" +__versionTime__ = "27 Sep 2019 10:27 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -117,6 +117,8 @@ import itertools from functools import wraps from itertools import filterfalse from threading import RLock +from contextlib import contextmanager +import unittest class __config_flags: @@ -217,7 +219,7 @@ __all__ = ['__version__', '__versionTime__', '__author__', '__compat__', '__diag 'stringStart', 'traceParseAction', 'unicodeString', 'withAttribute', 'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation', 'locatedExpr', 'withClass', 'CloseMatch', 'tokenMap', 'pyparsing_common', 'pyparsing_unicode', 'unicode_set', - 'conditionAsParseAction', + 'conditionAsParseAction', 'pyparsing_test', ] system_version = tuple(sys.version_info)[:3] @@ -6687,6 +6689,137 @@ pyparsing_unicode.ไทย = pyparsing_unicode.Thai pyparsing_unicode.देवनागरी = pyparsing_unicode.Devanagari +class pyparsing_test: + """ + namespace class for classes useful in writing unit tests + """ + class reset_pyparsing_context: + """ + Context manager to be used when writing unit tests that modify pyparsing config values: + - packrat parsing + - default whitespace characters + - default keyword characters + - literal string auto-conversion class + - __diag__ settings + + Example: + with reset_pyparsing_context(): + # test that literals used to construct a grammar are automatically suppressed + ParserElement.inlineLiteralsUsing(Suppress) + + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters are not included in the parsed tokens + self.assertParseAndCheckLisst(group, "(abc 123 def)", ['abc', '123', 'def']) + + # after exiting context manager, literals are converted to Literal expressions again + """ + def __init__(self): + self._save_context = {} + + def __enter__(self): + self._save_context['default_whitespace'] = ParserElement.DEFAULT_WHITE_CHARS + self._save_context['default_keyword_chars'] = Keyword.DEFAULT_KEYWORD_CHARS + self._save_context['literal_string_class'] = ParserElement._literalStringClass + self._save_context['packrat_enabled'] = ParserElement._packratEnabled + self._save_context['__diag__'] = {name: getattr(__diag__, name) for name in __diag__._all_names} + return self + + def __exit__(self, *args): + # reset pyparsing global state + if ParserElement.DEFAULT_WHITE_CHARS != self._save_context['default_whitespace']: + ParserElement.setDefaultWhitespaceChars(self._save_context['default_whitespace']) + Keyword.DEFAULT_KEYWORD_CHARS = self._save_context['default_keyword_chars'] + ParserElement.inlineLiteralsUsing(self._save_context['literal_string_class']) + for name, value in self._save_context['__diag__'].items(): + (__diag__.enable if value else __diag__.disable)(name) + ParserElement._packratEnabled = self._save_context['packrat_enabled'] + + + class ParseResultsAsserts(unittest.TestCase): + + def assertParseResultsEquals(self, result, expected_list=None, expected_dict=None, msg=None): + """ + Unit test assertion to compare a ParseResults object with an optional expected_list, + and compare any defined results names with an optional expected_dict. + """ + if expected_list is not None: + self.assertEqual(expected_list, result.asList(), msg=msg) + if expected_dict is not None: + self.assertEqual(expected_dict, result.asDict(), msg=msg) + + def assertParseAndCheckList(self, expr, test_string, expected_list, msg=None, verbose=True): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ParseResults.asList() is equal to the expected_list. + """ + result = expr.parseString(test_string, parseAll=True) + if verbose: + print(result.dump()) + self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) + + def assertParseAndCheckDict(self, expr, test_string, expected_dict, msg=None, verbose=True): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ParseResults.asDict() is equal to the expected_dict. + """ + result = expr.parseString(test_string, parseAll=True) + if verbose: + print(result.dump()) + self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) + + def assertRunTestResults(self, run_tests_report, expected_parse_results=None, msg=None): + """ + Unit test assertion to evaluate output of ParserElement.runTests(). If a list of + list-dict tuples is given as the expected_parse_results argument, then these are zipped + with the report tuples returned by runTests and evaluated using assertParseResultsEquals. + Finally, asserts that the overall runTests() success value is True. + + :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests + :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + """ + run_test_success, run_test_results = run_tests_report + + if expected_parse_results is not None: + merged = [(*rpt, expected) for rpt, expected in zip(run_test_results, expected_parse_results)] + for test_string, result, expected in merged: + # expected should be a tuple containing a list and/or a dict or an exception, + # and optional failure message string + # an empty tuple will skip any result validation + fail_msg = next((exp for exp in expected if isinstance(exp, str)), None) + expected_exception = next((exp for exp in expected + if isinstance(exp, type) and issubclass(exp, Exception)), None) + if expected_exception is not None: + with self.assertRaises(expected_exception=expected_exception, msg=fail_msg or msg): + if isinstance(result, Exception): + raise result + else: + expected_list = next((exp for exp in expected if isinstance(exp, list)), None) + expected_dict = next((exp for exp in expected if isinstance(exp, dict)), None) + if (expected_list, expected_dict) != (None, None): + self.assertParseResultsEquals(result, + expected_list=expected_list, expected_dict=expected_dict, + msg=fail_msg or msg) + else: + # warning here maybe? + print("no validation for {!r}".format(test_string)) + + # do this last, in case some specific test results can be reported instead + self.assertTrue(run_test_success, msg=msg if msg is not None else "failed runTests") + + @contextmanager + def assertRaisesParseException(self, exc_type=ParseException, msg=None): + with self.assertRaises(exc_type, msg=msg): + yield + + +# build list of built-in expressions, for future reference if a global default value +# gets updated +_builtin_exprs = [v for v in itertools.chain(vars().values(), vars(pyparsing_common).values()) + if isinstance(v, ParserElement)] + + if __name__ == "__main__": selectToken = CaselessLiteral("select") @@ -6757,8 +6890,3 @@ if __name__ == "__main__": pyparsing_common.uuid.runTests(""" 12345678-1234-5678-1234-567812345678 """) - -# build list of built-in expressions, for future reference if a global default value -# gets updated -_builtin_exprs = [v for v in itertools.chain(vars().values(), vars(pyparsing_common).values()) - if isinstance(v, ParserElement)] diff --git a/simple_unit_tests.py b/simple_unit_tests.py index 6fdab44..f0493cf 100644 --- a/simple_unit_tests.py +++ b/simple_unit_tests.py @@ -11,6 +11,7 @@ import unittest import pyparsing as pp from collections import namedtuple from datetime import datetime +ppt = pp.pyparsing_test # Test spec data class for specifying simple pyparsing test cases PpTestSpec = namedtuple("PpTestSpec", "desc expr text parse_fn " @@ -18,7 +19,7 @@ PpTestSpec = namedtuple("PpTestSpec", "desc expr text parse_fn " PpTestSpec.__new__.__defaults__ = ('', pp.Empty(), '', 'parseString', None, None, None) -class PyparsingExpressionTestCase(unittest.TestCase): +class PyparsingExpressionTestCase(ppt.ParseResultsAsserts, unittest.TestCase): """ Base pyparsing testing class to parse various pyparsing expressions against given text strings. Subclasses must define a class attribute 'tests' which @@ -50,10 +51,9 @@ class PyparsingExpressionTestCase(unittest.TestCase): if test_spec.parse_fn == 'parseString': print(result.dump()) # compare results against given list and/or dict - if test_spec.expected_list is not None: - self.assertEqual(result.asList(), test_spec.expected_list) - if test_spec.expected_dict is not None: - self.assertEqual(result.asDict(), test_spec.expected_dict) + self.assertParseResultsEquals(result, + expected_list=test_spec.expected_list, + expected_dict=test_spec.expected_dict) elif test_spec.parse_fn == 'transformString': print(result) # compare results against given list and/or dict @@ -66,13 +66,13 @@ class PyparsingExpressionTestCase(unittest.TestCase): self.assertEqual([result], test_spec.expected_list) else: # expect fail - try: - parsefn(test_spec.text) - except Exception as exc: - print(pp.ParseException.explain(exc)) - self.assertEqual(exc.loc, test_spec.expected_fail_locn) - else: - self.assertTrue(False, "failed to raise expected exception") + with self.assertRaisesParseException(): + try: + parsefn(test_spec.text) + except Exception as exc: + print(pp.ParseException.explain(exc)) + self.assertEqual(exc.loc, test_spec.expected_fail_locn) + raise # =========== TEST DEFINITIONS START HERE ============== @@ -1,5 +1,5 @@ [tox] -envlist=pypy, pypy3, py27, py33, py34, py35, py36 +envlist=pypy3, py35, py36, py37 [testenv] deps=-rrequirements-dev.txt diff --git a/unitTests.py b/unitTests.py index be70046..3bddbf4 100644 --- a/unitTests.py +++ b/unitTests.py @@ -11,6 +11,7 @@ from unittest import TestCase, TestSuite, TextTestRunner import datetime from pyparsing import ParseException import pyparsing as pp +ppt = pp.pyparsing_test import sys from io import StringIO @@ -60,7 +61,7 @@ class resetting(object): BUFFER_OUTPUT = True -class ParseTestCase(TestCase): +class ParseTestCase(ppt.ParseResultsAsserts, TestCase): def __init__(self): super().__init__(methodName='_runTest') @@ -75,7 +76,8 @@ class ParseTestCase(TestCase): sys.stdout = buffered_stdout sys.stderr = buffered_stdout print(">>>> Starting test", str(self)) - self.runTest() + with ppt.reset_pyparsing_context(): + self.runTest() finally: print("<<<< End of test", str(self)) @@ -525,7 +527,7 @@ class ParseCommaSeparatedValuesTest(ParseTestCase): for line, tests in zip(testData, testVals): print("Parsing: %r ->" % line, end=' ') results = ppc.comma_separated_list.parseString(line) - print(results.asList()) + print(results) for t in tests: if not(len(results) > t[0] and results[t[0]] == t[1]): print("$$$", results.dump()) @@ -991,19 +993,13 @@ class ParseExpressionResultsAccumulateTest(ParseTestCase): list_of_num=delimitedList(hexnum | num | name, ",") tokens = list_of_num.parseString('1, 0x2, 3, 0x4, aaa') - for k, llen, lst in (("base10", 2, ['1', '3']), - ("hex", 2, ['0x2', '0x4']), - ("word", 1, ['aaa'])): - print(k, tokens[k]) - self.assertEqual(len(tokens[k]), llen, "Wrong length for key %s, %s" % (k, str(tokens[k].asList()))) - self.assertEqual(lst, tokens[k].asList(), - "Incorrect list returned for key %s, %s" % (k, str(tokens[k].asList()))) - self.assertEqual(tokens.base10.asList(), ['1', '3'], - "Incorrect list for attribute base10, %s" % str(tokens.base10.asList())) - self.assertEqual(tokens.hex.asList(), ['0x2', '0x4'], - "Incorrect list for attribute hex, %s" % str(tokens.hex.asList())) - self.assertEqual(tokens.word.asList(), ['aaa'], - "Incorrect list for attribute word, %s" % str(tokens.word.asList())) + print(tokens.dump()) + self.assertParseResultsEquals(tokens, + expected_list=['1', '0x2', '3', '0x4', 'aaa'], + expected_dict={'base10': ['1', '3'], + 'hex': ['0x2', '0x4'], + 'word': ['aaa']}, + ) from pyparsing import Literal, Word, nums, Group, Dict, alphas, \ quotedString, oneOf, delimitedList, removeQuotes, alphanums @@ -1022,10 +1018,11 @@ class ParseExpressionResultsAccumulateTest(ParseTestCase): test="""Q(x,y,z):-Bloo(x,"Mitsis",y),Foo(y,z,1243),y>28,x<12,x>3""" queryRes = Query.parseString(test) - print("pred", queryRes.pred) - self.assertEqual(queryRes.pred.asList(), [['y', '>', '28'], ['x', '<', '12'], ['x', '>', '3']], - "Incorrect list for attribute pred, %s" % str(queryRes.pred.asList())) print(queryRes.dump()) + self.assertParseResultsEquals(queryRes.pred, + expected_list=[['y', '>', '28'], ['x', '<', '12'], ['x', '>', '3']], + msg="Incorrect list for attribute pred, %s" % str(queryRes.pred.asList()) + ) class ReStringRangeTest(ParseTestCase): def runTest(self): @@ -1121,14 +1118,12 @@ class SkipToParserTests(ParseTestCase): num_word = Word(nums, asKeyword=True).setName("int") def test(expr, test_string, expected_list, expected_dict): - try: - result = expr.parseString(test_string) - except Exception as pe: - if any(expected is not None for expected in (expected_list, expected_dict)): - self.assertTrue(False, "{} failed to parse {!r}".format(expr, test_string)) + if (expected_list, expected_dict) == (None, None): + with self.assertRaises(Exception, msg="{} failed to parse {!r}".format(expr, test_string)): + expr.parseString(test_string) else: - self.assertEqual(result.asList(), expected_list) - self.assertEqual(result.asDict(), expected_dict) + result = expr.parseString(test_string) + self.assertParseResultsEquals(result, expected_list=expected_list, expected_dict=expected_dict) # ellipses for SkipTo e = ... + Literal("end") @@ -1248,10 +1243,7 @@ class EllipsisRepetionWithResultsNamesTest(ParseTestCase): for obs, exp in zip(results, expected): test, result = obs exp_list, exp_dict = exp - self.assertEqual(result.asList(), exp_list, - "list mismatch {!r}, expected {!r}, actual {!r}".format(test, exp_list, result.asList())) - self.assertEqual(result.asDict(), exp_dict, - "dict mismatch {!r}, expected {!r}, actual {!r}".format(test, exp_dict, result.asDict())) + self.assertParseResultsEquals(result, expected_list=exp_list, expected_dict=exp_dict) parser = label('label') + val[...]('values') @@ -1268,10 +1260,7 @@ class EllipsisRepetionWithResultsNamesTest(ParseTestCase): for obs, exp in zip(results, expected): test, result = obs exp_list, exp_dict = exp - self.assertEqual(result.asList(), exp_list, - "list mismatch {!r}, expected {!r}, actual {!r}".format(test, exp_list, result.asList())) - self.assertEqual(result.asDict(), exp_dict, - "dict mismatch {!r}, expected {!r}, actual {!r}".format(test, exp_dict, result.asDict())) + self.assertParseResultsEquals(result, expected_list=exp_list, expected_dict=exp_dict) pt = pp.Group(val('x') + pp.Suppress(',') + val('y')) parser = label('label') + pt[...]("points") @@ -1290,10 +1279,7 @@ class EllipsisRepetionWithResultsNamesTest(ParseTestCase): for obs, exp in zip(results, expected): test, result = obs exp_list, exp_dict = exp - self.assertEqual(result.asList(), exp_list, - "list mismatch {!r}, expected {!r}, actual {!r}".format(test, exp_list, result.asList())) - self.assertEqual(result.asDict(), exp_dict, - "dict mismatch {!r}, expected {!r}, actual {!r}".format(test, exp_dict, result.asDict())) + self.assertParseResultsEquals(result, expected_list=exp_list, expected_dict=exp_dict) class CustomQuotesTest(ParseTestCase): @@ -1386,7 +1372,7 @@ class RepeaterTest(ParseTestCase): for tst, result in tests: found = False for tokens, start, end in seq.scanString(tst): - print(tokens.asList()) + print(tokens) found = True if not found: print("No expression match in", tst) @@ -1413,7 +1399,7 @@ class RepeaterTest(ParseTestCase): for tst, result in tests: found = False for tokens, start, end in compoundSeq.scanString(tst): - print("match:", tokens.asList()) + print("match:", tokens) found = True break if not found: @@ -1433,7 +1419,7 @@ class RepeaterTest(ParseTestCase): for tst, result in tests: found = False for tokens, start, end in eSeq.scanString(tst): - print(tokens.asList()) + print(tokens) found = True if not found: print("No match in", tst) @@ -1446,15 +1432,15 @@ class RecursiveCombineTest(ParseTestCase): testInput = "myc(114)r(11)dd" Stream=Forward() Stream << Optional(Word(alphas)) + Optional("(" + Word(nums) + ")" + Stream) - expected = Stream.parseString(testInput).asList() - print(["".join(expected)]) + expected = [''.join(Stream.parseString(testInput))] + print(expected) Stream=Forward() Stream << Combine(Optional(Word(alphas)) + Optional("(" + Word(nums) + ")" + Stream)) - testVal = Stream.parseString(testInput).asList() + testVal = Stream.parseString(testInput) print(testVal) - self.assertEqual("".join(testVal), "".join(expected), "Failed to process Combine with recursive content") + self.assertParseResultsEquals(testVal, expected_list=expected) class InfixNotationGrammarTest1(ParseTestCase): def runTest(self): @@ -1502,10 +1488,12 @@ class InfixNotationGrammarTest1(ParseTestCase): [[1, '+', [2, '*', ['-', [3, '^', 4]], '*', 5], '+', ['-', ['+', ['-', 6]]]]] [[3, '!', '!']]""".split('\n') expected = [ast.literal_eval(x.strip()) for x in expected] - for t, e in zip(test, expected): - print(t, "->", e, "got", expr.parseString(t).asList()) - self.assertEqual(expr.parseString(t).asList(), e, - "mismatched results for infixNotation: got %s, expected %s" % (expr.parseString(t).asList(), e)) + for test_str, exp_list in zip(test, expected): + result = expr.parseString(test_str) + print(test_str, "->", exp_list, "got", result) + self.assertParseResultsEquals(result, expected_list=exp_list, + msg="mismatched results for infixNotation: got %s, expected %s" % + (result.asList(), exp_list)) class InfixNotationGrammarTest2(ParseTestCase): def runTest(self): @@ -1583,8 +1571,10 @@ class InfixNotationGrammarTest2(ParseTestCase): print("r =", boolVars["r"]) print() for t in test: - res = boolExpr.parseString(t)[0] - print(t, '\n', res, '=', bool(res), '\n') + res = boolExpr.parseString(t) + print(t, '\n', res[0], '=', bool(res[0]), '\n') + expected = eval(t, {}, boolVars) + self.assertEquals(expected, bool(res[0])) class InfixNotationGrammarTest3(ParseTestCase): @@ -1647,14 +1637,14 @@ class InfixNotationGrammarTest4(ParseTestCase): f = booleanExpr(word) + pp.StringEnd() tests = [ - ("bar = foo", "[['bar', '=', 'foo']]"), - ("bar = foo & baz = fee", "['&', [['bar', '=', 'foo'], ['baz', '=', 'fee']]]"), + ("bar = foo", [['bar', '=', 'foo']]), + ("bar = foo & baz = fee", ['&', [['bar', '=', 'foo'], ['baz', '=', 'fee']]]), ] for test, expected in tests: print(test) results = f.parseString(test) print(results) - self.assertEqual(str(results), expected, "failed to match expected results, got '%s'" % str(results)) + self.assertParseResultsEquals(results, expected_list=expected) print() class InfixNotationGrammarTest5(ParseTestCase): @@ -1805,8 +1795,9 @@ class ParseResultsWithNamedTupleTest(ParseTestCase): res = expr.parseString("A") print(repr(res)) print(res.Achar) - self.assertEqual(res.Achar, ("A", "Z"), - "Failed accessing named results containing a tuple, got {!r}".format(res.Achar)) + self.assertParseResultsEquals(res, expected_dict={'Achar': ('A', 'Z')}, + msg="Failed accessing named results containing a tuple, " + "got {!r}".format(res.Achar)) class ParseHTMLTagsTest(ParseTestCase): @@ -1831,7 +1822,7 @@ class ParseHTMLTagsTest(ParseTestCase): bodyStart, bodyEnd = pp.makeHTMLTags("BODY") resIter = iter(results) for t, s, e in (bodyStart | bodyEnd).scanString(test): - print(test[s:e], "->", t.asList()) + print(test[s:e], "->", t) (expectedType, expectedEmpty, expectedBG, expectedFG) = next(resIter) print(t.dump()) @@ -1876,11 +1867,6 @@ class UpcaseDowncaseUnicode(ParseTestCase): kw = pp.Keyword('mykey', caseless=True).setParseAction(ppc.upcaseTokens)('rname') ret = kw.parseString('mykey') print(ret.rname) - self.assertEqual(ret.rname, 'MYKEY', "failed to upcase with named result") - - kw = pp.Keyword('mykey', caseless=True).setParseAction(ppc.upcaseTokens)('rname') - ret = kw.parseString('mykey') - print(ret.rname) self.assertEqual(ret.rname, 'MYKEY', "failed to upcase with named result (pyparsing_common)") kw = pp.Keyword('MYKEY', caseless=True).setParseAction(ppc.downcaseTokens)('rname') @@ -1966,7 +1952,7 @@ class ParseUsingRegex(ParseTestCase): self.assertTrue(testMatch(namedGrouping, '"foo bar" baz', True, '"foo bar"'), "Re: (16) failed, expected pass") ret = namedGrouping.parseString('"zork" blah') - print(ret.asList()) + print(ret) print(list(ret.items())) print(ret.content) self.assertEqual(ret.content, 'zork', "named group lookup failed") @@ -1996,7 +1982,8 @@ class RegexAsTypeTest(ParseTestCase): result = expr.parseString(test_str) print(result.dump()) print(expected_group_list) - self.assertEqual(result.asList(), expected_group_list, "incorrect group list returned by Regex)") + self.assertParseResultsEquals(result, expected_list=expected_group_list, + msg="incorrect group list returned by Regex)") print("return as re.match instance") expr = pp.Regex(r"\w+ (?P<num1>\d+) (?P<num2>\d+) (?P<last_word>\w+)", asMatch=True) @@ -2084,16 +2071,8 @@ class PrecededByTest(ParseTestCase): ]: print(expr.searchString(s)) result = sum(expr.searchString(s)) - print(result) - - self.assertEqual(result.asList(), expected_list, - "Erroneous tokens for {}: expected {}, got {}".format(expr, - expected_list, - result.asList())) - self.assertEqual(result.asDict(), expected_dict, - "Erroneous named results for {}: expected {}, got {}".format(expr, - expected_dict, - result.asDict())) + print(result.dump()) + self.assertParseResultsEquals(result, expected_list, expected_dict) class CountedArrayTest(ParseTestCase): def runTest(self): @@ -2106,10 +2085,9 @@ class CountedArrayTest(ParseTestCase): r = OneOrMore(countedField).parseString(testString) print(testString) - print(r.asList()) + print(r) - self.assertEqual(r.asList(), [[5, 7], [0, 1, 2, 3, 4, 5], [], [5, 4, 3]], - "Failed matching countedArray, got " + str(r.asList())) + self.assertParseResultsEquals(r, expected_list=[[5, 7], [0, 1, 2, 3, 4, 5], [], [5, 4, 3]]) class CountedArrayTest2(ParseTestCase): # addresses bug raised by Ralf Vosseler @@ -2124,10 +2102,9 @@ class CountedArrayTest2(ParseTestCase): dummy = Word("A") r = OneOrMore(dummy ^ countedField).parseString(testString) print(testString) - print(r.asList()) + print(r) - self.assertEqual(r.asList(), [[5, 7], [0, 1, 2, 3, 4, 5], [], [5, 4, 3]], - "Failed matching countedArray, got " + str(r.asList())) + self.assertParseResultsEquals(r, expected_list=[[5, 7], [0, 1, 2, 3, 4, 5], [], [5, 4, 3]]) class CountedArrayTest3(ParseTestCase): # test case where counter is not a decimal integer @@ -2144,10 +2121,9 @@ class CountedArrayTest3(ParseTestCase): r = OneOrMore(countedField).parseString(testString) print(testString) - print(r.asList()) + print(r) - self.assertEqual(r.asList(), [[5, 7], [0, 1, 2, 3, 4, 5], [], [5, 4, 3]], - "Failed matching countedArray, got " + str(r.asList())) + self.assertParseResultsEquals(r, expected_list=[[5, 7], [0, 1, 2, 3, 4, 5], [], [5, 4, 3]]) class LineStartTest(ParseTestCase): def runTest(self): @@ -2257,13 +2233,13 @@ class LineAndStringEndTest(ParseTestCase): for test, expected in tests: res1 = bnf1.parseString(test) print(res1, '=?', expected) - self.assertEqual(res1.asList(), expected, - "Failed lineEnd/stringEnd test (1): " + repr(test)+ " -> " + str(res1.asList())) + self.assertParseResultsEquals(res1, expected_list=expected, + msg="Failed lineEnd/stringEnd test (1): " + repr(test)+ " -> " + str(res1)) res2 = bnf2.searchString(test)[0] - print(res2.asList(), '=?', expected[-1:]) - self.assertEqual(res2.asList(), expected[-1:], - "Failed lineEnd/stringEnd test (2): " + repr(test)+ " -> " + str(res2.asList())) + print(res2, '=?', expected[-1:]) + self.assertParseResultsEquals(res2, expected_list=expected[-1:], + msg="Failed lineEnd/stringEnd test (2): " + repr(test)+ " -> " + str(res2)) res3 = bnf3.parseString(test) first = res3[0] @@ -2289,16 +2265,15 @@ class LineAndStringEndTest(ParseTestCase): ] for i, (src, expected) in enumerate(tests): print(i, repr(src).replace('\\\\', '\\'), end=' ') - try: - res = k.parseString(src, parseAll=True).asList() - except ParseException as pe: - res = None - print(res) - self.assertEqual(res, expected, "Failed on parseAll=True test %d" % i) + if expected is None: + with self.assertRaisesParseException(): + k.parseString(src, parseAll=True) + else: + res = k.parseString(src, parseAll=True) + self.assertParseResultsEquals(res, expected, msg="Failed on parseAll=True test %d" % i) class VariableParseActionArgsTest(ParseTestCase): def runTest(self): - pa3 = lambda s, l, t: t pa2 = lambda l, t: t pa1 = lambda t: t @@ -2439,8 +2414,9 @@ class VariableParseActionArgsTest(ParseTestCase): I | J | K | L | M | N | O | P | Q | R | S | U | V | B | T) testString = "VUTSRQPONMLKJIHGFEDCBA" res = gg.parseString(testString) - print(res.asList()) - self.assertEqual(res.asList(), list(testString), "Failed to parse using variable length parse actions") + print(res) + self.assertParseResultsEquals(res, expected_list=list(testString), + msg="Failed to parse using variable length parse actions") A = Literal("A").setParseAction(ClassAsPA0) B = Literal("B").setParseAction(ClassAsPA1) @@ -2457,7 +2433,7 @@ class VariableParseActionArgsTest(ParseTestCase): "Failed to parse using variable length parse actions " "using class constructors as parse actions") -class EnablePackratParsing(ParseTestCase): +class EnablePackratParsing(TestCase): def runTest(self): from pyparsing import ParserElement ParserElement.enablePackrat() @@ -2742,10 +2718,11 @@ class ParseAllTest(ParseTestCase): ] for s, parseAllFlag, shouldSucceed in tests: try: - print("'%s' parseAll=%s (shouldSuceed=%s)" % (s, parseAllFlag, shouldSucceed)) - testExpr.parseString(s, parseAllFlag) + print("'%s' parseAll=%s (shouldSucceed=%s)" % (s, parseAllFlag, shouldSucceed)) + testExpr.parseString(s, parseAll=parseAllFlag) self.assertTrue(shouldSucceed, "successfully parsed when should have failed") except ParseException as pe: + print(pe.explain(pe)) self.assertFalse(shouldSucceed, "failed to parse when should have succeeded") # add test for trailing comments @@ -2760,11 +2737,33 @@ class ParseAllTest(ParseTestCase): for s, parseAllFlag, shouldSucceed in tests: try: print("'%s' parseAll=%s (shouldSucceed=%s)" % (s, parseAllFlag, shouldSucceed)) - testExpr.parseString(s, parseAllFlag) + testExpr.parseString(s, parseAll=parseAllFlag) + self.assertTrue(shouldSucceed, "successfully parsed when should have failed") + except ParseException as pe: + print(pe.explain(pe)) + self.assertFalse(shouldSucceed, "failed to parse when should have succeeded") + + # add test with very long expression string + # testExpr = pp.MatchFirst([pp.Literal(c) for c in pp.printables if c != 'B'])[1, ...] + anything_but_an_f = pp.OneOrMore(pp.MatchFirst([pp.Literal(c) for c in pp.printables if c != 'f'])) + testExpr = pp.Word('012') + anything_but_an_f + + tests = [ + ("00aab", False, True), + ("00aab", True, True), + ("00aaf", False, True), + ("00aaf", True, False), + ] + for s, parseAllFlag, shouldSucceed in tests: + try: + print("'%s' parseAll=%s (shouldSucceed=%s)" % (s, parseAllFlag, shouldSucceed)) + testExpr.parseString(s, parseAll=parseAllFlag) self.assertTrue(shouldSucceed, "successfully parsed when should have failed") except ParseException as pe: + print(pe.explain(pe)) self.assertFalse(shouldSucceed, "failed to parse when should have succeeded") + class GreedyQuotedStringsTest(ParseTestCase): def runTest(self): from pyparsing import QuotedString, sglQuotedString, dblQuotedString, quotedString, delimitedList @@ -3685,7 +3684,7 @@ class NumericExpressionsTest(ParseTestCase): # success, test_results = expr.runTests(sorted(tests, key=len), failureTests=is_fail, **suppress_results) # filter_result_fn = (lambda r: isinstance(r, Exception), # lambda r: not isinstance(r, Exception))[is_fail] - # print(expr, ('FAIL', 'PASS')[success], "{} valid tests ({})".format(len(tests), + # print(expr, ('FAIL', 'PASS')[success], "{}valid tests ({})".format(len(tests), # 'in' if is_fail else '')) # if not success: # all_pass = False @@ -3709,7 +3708,7 @@ class NumericExpressionsTest(ParseTestCase): print(t, "should not fail but did") success = False print(expr, ('FAIL', 'PASS')[success], "{}valid tests ({})".format('in' if is_fail else '', - len(tests))) + len(tests),)) all_pass = all_pass and success self.assertTrue(all_pass, "failed one or more numeric tests") @@ -3719,12 +3718,26 @@ class TokenMapTest(ParseTestCase): from pyparsing import tokenMap, Word, hexnums, OneOrMore parser = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16)) - success, results = parser.runTests(""" + success, report = parser.runTests(""" 00 11 22 aa FF 0a 0d 1a - """, printResults=False) - self.assertTrue(success, "failed to parse hex integers") - print(results) - self.assertEqual(results[0][-1].asList(), [0, 17, 34, 170, 255, 10, 13, 26], "tokenMap parse action failed") + """) + # WAS: + # self.assertTrue(success, "failed to parse hex integers") + # print(results) + # self.assertEqual(results[0][-1].asList(), [0, 17, 34, 170, 255, 10, 13, 26], "tokenMap parse action failed") + + # USING JUST assertParseResultsEquals + # results = [rpt[1] for rpt in report] + # self.assertParseResultsEquals(results[0], [0, 17, 34, 170, 255, 10, 13, 26], + # msg="tokenMap parse action failed") + + # if I hadn't unpacked the return from runTests, I could have just passed it directly, + # instead of reconstituting as a tuple + self.assertRunTestResults((success, report), + [ + ([0, 17, 34, 170, 255, 10, 13, 26], "tokenMap parse action failed"), + ], + msg="failed to parse hex integers") class ParseFileTest(ParseTestCase): @@ -3860,56 +3873,75 @@ class ParseFatalExceptionTest(ParseTestCase): from pyparsing import Word, nums, ParseFatalException - success = False - try: + with self.assertRaisesParseException(exc_type=ParseFatalException, + msg="failed to raise ErrorStop exception"): expr = "ZZZ" - Word(nums) expr.parseString("ZZZ bad") - except ParseFatalException as pfe: - print('ParseFatalException raised correctly') - success = True - except Exception as e: - print(type(e)) - print(e) - self.assertTrue(success, "bad handling of syntax error") + # WAS: + # success = False + # try: + # expr = "ZZZ" - Word(nums) + # expr.parseString("ZZZ bad") + # except ParseFatalException as pfe: + # print('ParseFatalException raised correctly') + # success = True + # except Exception as e: + # print(type(e)) + # print(e) + # + # self.assertTrue(success, "bad handling of syntax error") class InlineLiteralsUsingTest(ParseTestCase): def runTest(self): from pyparsing import ParserElement, Suppress, Literal, CaselessLiteral, Word, alphas, oneOf, CaselessKeyword, nums - with resetting(ParserElement, "_literalStringClass"): - ParserElement.inlineLiteralsUsing(Suppress) - wd = Word(alphas) - result = (wd + ',' + wd + oneOf("! . ?")).parseString("Hello, World!") - self.assertEqual(len(result), 3, "inlineLiteralsUsing(Suppress) failed!") - - ParserElement.inlineLiteralsUsing(Literal) - result = (wd + ',' + wd + oneOf("! . ?")).parseString("Hello, World!") - self.assertEqual(len(result), 4, "inlineLiteralsUsing(Literal) failed!") - - ParserElement.inlineLiteralsUsing(CaselessKeyword) - result = ("SELECT" + wd + "FROM" + wd).parseString("select color from colors") - self.assertEqual(result.asList(), "SELECT color FROM colors".split(), - "inlineLiteralsUsing(CaselessKeyword) failed!") - - ParserElement.inlineLiteralsUsing(CaselessLiteral) - result = ("SELECT" + wd + "FROM" + wd).parseString("select color from colors") - self.assertEqual(result.asList(), "SELECT color FROM colors".split(), - "inlineLiteralsUsing(CaselessLiteral) failed!") - - integer = Word(nums) - ParserElement.inlineLiteralsUsing(Literal) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - result = date_str.parseString("1999/12/31") - self.assertEqual(result.asList(), ['1999', '/', '12', '/', '31'], "inlineLiteralsUsing(example 1) failed!") + wd = Word(alphas) + + ParserElement.inlineLiteralsUsing(Suppress) + result = (wd + ',' + wd + oneOf("! . ?")).parseString("Hello, World!") + self.assertEqual(len(result), 3, "inlineLiteralsUsing(Suppress) failed!") + + ParserElement.inlineLiteralsUsing(Literal) + result = (wd + ',' + wd + oneOf("! . ?")).parseString("Hello, World!") + self.assertEqual(len(result), 4, "inlineLiteralsUsing(Literal) failed!") + + ParserElement.inlineLiteralsUsing(CaselessKeyword) + # WAS: + # result = ("SELECT" + wd + "FROM" + wd).parseString("select color from colors") + # self.assertEqual(result.asList(), "SELECT color FROM colors".split(), + # "inlineLiteralsUsing(CaselessKeyword) failed!") + self.assertParseAndCheckList("SELECT" + wd + "FROM" + wd, + "select color from colors", + expected_list=['SELECT', 'color', 'FROM', 'colors'], + msg="inlineLiteralsUsing(CaselessKeyword) failed!") + + ParserElement.inlineLiteralsUsing(CaselessLiteral) + # result = ("SELECT" + wd + "FROM" + wd).parseString("select color from colors") + # self.assertEqual(result.asList(), "SELECT color FROM colors".split(), + # "inlineLiteralsUsing(CaselessLiteral) failed!") + self.assertParseAndCheckList("SELECT" + wd + "FROM" + wd, + "select color from colors", + expected_list=['SELECT', 'color', 'FROM', 'colors'], + msg="inlineLiteralsUsing(CaselessLiteral) failed!") - # change to Suppress - ParserElement.inlineLiteralsUsing(Suppress) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString("1999/12/31") # -> ['1999', '12', '31'] - self.assertEqual(result.asList(), ['1999', '12', '31'], "inlineLiteralsUsing(example 2) failed!") + integer = Word(nums) + ParserElement.inlineLiteralsUsing(Literal) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + # result = date_str.parseString("1999/12/31") + # self.assertEqual(result.asList(), ['1999', '/', '12', '/', '31'], "inlineLiteralsUsing(example 1) failed!") + self.assertParseAndCheckList(date_str, "1999/12/31", expected_list=['1999', '/', '12', '/', '31'], + msg="inlineLiteralsUsing(example 1) failed!") + + # change to Suppress + ParserElement.inlineLiteralsUsing(Suppress) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + # result = date_str.parseString("1999/12/31") # -> ['1999', '12', '31'] + # self.assertEqual(result.asList(), ['1999', '12', '31'], "inlineLiteralsUsing(example 2) failed!") + self.assertParseAndCheckList(date_str, "1999/12/31", expected_list=['1999', '12', '31'], + msg="inlineLiteralsUsing(example 2) failed!") class CloseMatchTest(ParseTestCase): def runTest(self): @@ -3945,19 +3977,13 @@ class DefaultKeywordCharsTest(ParseTestCase): def runTest(self): import pyparsing as pp - try: + with self.assertRaisesParseException(msg="failed to fail matching keyword using updated keyword chars"): pp.Keyword("start").parseString("start1000") - except pp.ParseException: - pass - else: - self.assertTrue(False, "failed to fail on default keyword chars") try: pp.Keyword("start", identChars=pp.alphas).parseString("start1000") except pp.ParseException: self.assertTrue(False, "failed to match keyword using updated keyword chars") - else: - pass with resetting(pp.Keyword, "DEFAULT_KEYWORD_CHARS"): pp.Keyword.setDefaultKeywordChars(pp.alphas) @@ -3965,22 +3991,14 @@ class DefaultKeywordCharsTest(ParseTestCase): pp.Keyword("start").parseString("start1000") except pp.ParseException: self.assertTrue(False, "failed to match keyword using updated keyword chars") - else: - pass - try: + with self.assertRaisesParseException(msg="failed to fail matching keyword using updated keyword chars"): pp.CaselessKeyword("START").parseString("start1000") - except pp.ParseException: - pass - else: - self.assertTrue(False, "failed to fail on default keyword chars") try: pp.CaselessKeyword("START", identChars=pp.alphas).parseString("start1000") except pp.ParseException: self.assertTrue(False, "failed to match keyword using updated keyword chars") - else: - pass with resetting(pp.Keyword, "DEFAULT_KEYWORD_CHARS"): pp.Keyword.setDefaultKeywordChars(pp.alphas) @@ -3988,8 +4006,6 @@ class DefaultKeywordCharsTest(ParseTestCase): pp.CaselessKeyword("START").parseString("start1000") except pp.ParseException: self.assertTrue(False, "failed to match keyword using updated keyword chars") - else: - pass class ColTest(ParseTestCase): def runTest(self): @@ -4053,7 +4069,8 @@ class ParseActionNestingTest(ParseTestCase): vals.addParseAction(add_total) results = vals.parseString("244 23 13 2343") print(results.dump()) - self.assertEqual(results.int_values.asDict(), {}, "noop parse action changed ParseResults structure") + self.assertParseResultsEquals(results, expected_dict={'int_values': [244, 23, 13, 2343], 'total': 2623}, + msg="noop parse action changed ParseResults structure") name = pp.Word(pp.alphas)('name') score = pp.Word(pp.nums + '.')('score') @@ -4107,15 +4124,12 @@ class ParseResultsNamesInGroupWithDictTest(ParseTestCase): test_string = '"Golden Gate Bridge" 37.819722 -122.478611 height=746 span=4200' site.runTests(test_string) - # U = list_num.parseString(test_string) - # self.assertTrue("LIT_NUM" not in U.LIST.LIST_VALUES, "results name retained as sub in ungrouped named result") - a, aEnd = pp.makeHTMLTags('a') attrs = a.parseString("<a href='blah'>") print(attrs.dump()) - self.assertEqual(attrs.startA.href, 'blah') - self.assertEqual(attrs.asDict(), {'startA': {'href': 'blah', 'tag': 'a', 'empty': False}, - 'href': 'blah', 'tag': 'a', 'empty': False}) + self.assertParseResultsEquals(attrs, + expected_dict = {'startA': {'href': 'blah', 'tag': 'a', 'empty': False}, + 'href': 'blah', 'tag': 'a', 'empty': False}) class FollowedByTest(ParseTestCase): @@ -4193,8 +4207,10 @@ class UnicodeTests(ParseTestCase): hello = "Καλημέρα, κόσμε!" result = greet.parseString(hello) print(result) - self.assertTrue(result.asList() == ['Καλημέρα', ',', 'κόσμε', '!'], - "Failed to parse Greek 'Hello, World!' using pyparsing_unicode.Greek.alphas") + self.assertParseResultsEquals(result, + expected_list=['Καλημέρα', ',', 'κόσμε', '!'], + msg="Failed to parse Greek 'Hello, World!' using " + "pyparsing_unicode.Greek.alphas") # define a custom unicode range using multiple inheritance class Turkish_set(ppu.Latin1, ppu.LatinA): @@ -4223,9 +4239,10 @@ class UnicodeTests(ParseTestCase): nüfus=4279677""" result = pp.Dict(pp.OneOrMore(pp.Group(key_value))).parseString(sample) - print(result.asDict()) - self.assertEqual(result.asDict(), {'şehir': 'İzmir', 'ülke': 'Türkiye', 'nüfus': 4279677}, - "Failed to parse Turkish key-value pairs") + print(result.dump()) + self.assertParseResultsEquals(result, + expected_dict={'şehir': 'İzmir', 'ülke': 'Türkiye', 'nüfus': 4279677}, + msg="Failed to parse Turkish key-value pairs") class IndentedBlockExampleTest(ParseTestCase): @@ -4508,12 +4525,18 @@ class ParseResultsWithNameMatchFirst(ParseTestCase): expr_a = pp.Literal('not') + pp.Literal('the') + pp.Literal('bird') expr_b = pp.Literal('the') + pp.Literal('bird') expr = (expr_a | expr_b)('rexp') - expr.runTests("""\ + + success, report = expr.runTests("""\ not the bird the bird """) - self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split()) - self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split()) + results = [rpt[1] for rpt in report] + self.assertParseResultsEquals(results[0], + ['not', 'the', 'bird'], + {'rexp': ['not', 'the', 'bird']}) + self.assertParseResultsEquals(results[1], + ['the', 'bird'], + {'rexp': ['the', 'bird']}) # test compatibility mode, no longer restoring pre-2.3.1 behavior with resetting(pp.__compat__, "collect_all_And_tokens"), \ @@ -4525,12 +4548,17 @@ class ParseResultsWithNameMatchFirst(ParseTestCase): with self.assertWarns(UserWarning, msg="failed to warn of And within alternation"): expr = (expr_a | expr_b)('rexp') - expr.runTests(""" + success, report = expr.runTests(""" not the bird the bird """) - self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split()) - self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split()) + results = [rpt[1] for rpt in report] + self.assertParseResultsEquals(results[0], + ['not', 'the', 'bird'], + {'rexp': ['not', 'the', 'bird']}) + self.assertParseResultsEquals(results[1], + ['the', 'bird'], + {'rexp': ['the', 'bird']}) class ParseResultsWithNameOr(ParseTestCase): @@ -4543,16 +4571,29 @@ class ParseResultsWithNameOr(ParseTestCase): not the bird the bird """) - self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split()) - self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split()) + result = expr.parseString('not the bird') + self.assertParseResultsEquals(result, + ['not', 'the', 'bird'], + {'rexp': ['not', 'the', 'bird']}) + result = expr.parseString('the bird') + self.assertParseResultsEquals(result, + ['the', 'bird'], + {'rexp': ['the', 'bird']}) expr = (expr_a | expr_b)('rexp') expr.runTests("""\ not the bird the bird """) - self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split()) - self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split()) + result = expr.parseString('not the bird') + self.assertParseResultsEquals(result, + ['not', 'the', 'bird'], + {'rexp': + ['not', 'the', 'bird']}) + result = expr.parseString('the bird') + self.assertParseResultsEquals(result, + ['the', 'bird'], + {'rexp': ['the', 'bird']}) # test compatibility mode, no longer restoring pre-2.3.1 behavior with resetting(pp.__compat__, "collect_all_And_tokens"), \ @@ -5118,7 +5159,6 @@ def makeTestSuite(): test_case_classes.remove(PyparsingTestInit) # test_case_classes.remove(ParseASMLTest) - test_case_classes.remove(EnablePackratParsing) if IRON_PYTHON_ENV: test_case_classes.remove(OriginalTextForTest) |