CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
testing.py399 linesDownload Raw Back to pyparsing
1# testing.py2 3from contextlib import contextmanager4import re5import typing6import unittest7 8 9from .core import (10    ParserElement,11    ParseException,12    Keyword,13    __diag__,14    __compat__,15)16from . import core_builtin_exprs17 18 19class pyparsing_test:20    """21    namespace class for classes useful in writing unit tests22    """23 24    class reset_pyparsing_context:25        """26        Context manager to be used when writing unit tests that modify pyparsing config values:27        - packrat parsing28        - bounded recursion parsing29        - default whitespace characters30        - default keyword characters31        - literal string auto-conversion class32        - ``__diag__`` settings33 34        Example:35 36        .. testcode::37 38            ppt = pyparsing.pyparsing_test39 40            class MyTestClass(ppt.TestParseResultsAsserts):41                def test_literal(self):42                    with ppt.reset_pyparsing_context():43                        # test that literals used to construct44                        # a grammar are automatically suppressed45                        ParserElement.inline_literals_using(Suppress)46 47                        term = Word(alphas) | Word(nums)48                        group = Group('(' + term[...] + ')')49 50                        # assert that the '()' characters51                        # are not included in the parsed tokens52                        self.assertParseAndCheckList(53                            group,54                            "(abc 123 def)",55                            ['abc', '123', 'def']56                        )57 58                    # after exiting context manager, literals59                    # are converted to Literal expressions again60        """61 62        def __init__(self):63            self._save_context = {}64 65        def save(self):66            self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS67            self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS68 69            self._save_context["literal_string_class"] = (70                ParserElement._literalStringClass71            )72 73            self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace74 75            self._save_context["packrat_enabled"] = ParserElement._packratEnabled76            if ParserElement._packratEnabled:77                self._save_context["packrat_cache_size"] = (78                    ParserElement.packrat_cache.size79                )80            else:81                self._save_context["packrat_cache_size"] = None82            self._save_context["packrat_parse"] = ParserElement._parse83            self._save_context["recursion_enabled"] = (84                ParserElement._left_recursion_enabled85            )86 87            self._save_context["__diag__"] = {88                name: getattr(__diag__, name) for name in __diag__._all_names89            }90 91            self._save_context["__compat__"] = {92                "collect_all_And_tokens": __compat__.collect_all_And_tokens93            }94 95            return self96 97        def restore(self):98            # reset pyparsing global state99            if (100                ParserElement.DEFAULT_WHITE_CHARS101                != self._save_context["default_whitespace"]102            ):103                ParserElement.set_default_whitespace_chars(104                    self._save_context["default_whitespace"]105                )106 107            ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"]108 109            Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"]110            ParserElement.inline_literals_using(111                self._save_context["literal_string_class"]112            )113 114            for name, value in self._save_context["__diag__"].items():115                (__diag__.enable if value else __diag__.disable)(name)116 117            ParserElement._packratEnabled = False118            if self._save_context["packrat_enabled"]:119                ParserElement.enable_packrat(self._save_context["packrat_cache_size"])120            else:121                ParserElement._parse = self._save_context["packrat_parse"]122            ParserElement._left_recursion_enabled = self._save_context[123                "recursion_enabled"124            ]125 126            # clear debug flags on all builtins127            for expr in core_builtin_exprs:128                expr.set_debug(False)129 130            __compat__.collect_all_And_tokens = self._save_context["__compat__"]131 132            return self133 134        def copy(self):135            ret = type(self)()136            ret._save_context.update(self._save_context)137            return ret138 139        def __enter__(self):140            return self.save()141 142        def __exit__(self, *args):143            self.restore()144 145    class TestParseResultsAsserts(unittest.TestCase):146        """147        A mixin class to add parse results assertion methods to normal unittest.TestCase classes.148        """149 150        def assertParseResultsEquals(151            self, result, expected_list=None, expected_dict=None, msg=None152        ):153            """154            Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``,155            and compare any defined results names with an optional ``expected_dict``.156            """157            if expected_list is not None:158                self.assertEqual(expected_list, result.as_list(), msg=msg)159            if expected_dict is not None:160                self.assertEqual(expected_dict, result.as_dict(), msg=msg)161 162        def assertParseAndCheckList(163            self, expr, test_string, expected_list, msg=None, verbose=True164        ):165            """166            Convenience wrapper assert to test a parser element and input string, and assert that167            the resulting :meth:`ParseResults.as_list` is equal to the ``expected_list``.168            """169            result = expr.parse_string(test_string, parse_all=True)170            if verbose:171                print(result.dump())172            else:173                print(result.as_list())174            self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg)175 176        def assertParseAndCheckDict(177            self, expr, test_string, expected_dict, msg=None, verbose=True178        ):179            """180            Convenience wrapper assert to test a parser element and input string, and assert that181            the resulting :meth:`ParseResults.as_dict` is equal to the ``expected_dict``.182            """183            result = expr.parse_string(test_string, parse_all=True)184            if verbose:185                print(result.dump())186            else:187                print(result.as_list())188            self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg)189 190        def assertRunTestResults(191            self, run_tests_report, expected_parse_results=None, msg=None192        ):193            """194            Unit test assertion to evaluate output of195            :meth:`~ParserElement.run_tests`.196 197            If a list of list-dict tuples is given as the198            ``expected_parse_results`` argument, then these are zipped199            with the report tuples returned by ``run_tests()``200            and evaluated using :meth:`assertParseResultsEquals`.201            Finally, asserts that the overall202            `:meth:~ParserElement.run_tests` success value is ``True``.203 204            :param run_tests_report: the return value from :meth:`ParserElement.run_tests`205            :type run_tests_report: tuple[bool, list[tuple[str, ParseResults | Exception]]]206            :param expected_parse_results: (optional)207            :type expected_parse_results: list[tuple[str | list | dict | Exception, ...]]208            """209            run_test_success, run_test_results = run_tests_report210 211            if expected_parse_results is None:212                self.assertTrue(213                    run_test_success, msg=msg if msg is not None else "failed runTests"214                )215                return216 217            merged = [218                (*rpt, expected)219                for rpt, expected in zip(run_test_results, expected_parse_results)220            ]221            for test_string, result, expected in merged:222                # expected should be a tuple containing a list and/or a dict or an exception,223                # and optional failure message string224                # an empty tuple will skip any result validation225                fail_msg = next((exp for exp in expected if isinstance(exp, str)), None)226                expected_exception = next(227                    (228                        exp229                        for exp in expected230                        if isinstance(exp, type) and issubclass(exp, Exception)231                    ),232                    None,233                )234                if expected_exception is not None:235                    with self.assertRaises(236                        expected_exception=expected_exception, msg=fail_msg or msg237                    ):238                        if isinstance(result, Exception):239                            raise result240                else:241                    expected_list = next(242                        (exp for exp in expected if isinstance(exp, list)), None243                    )244                    expected_dict = next(245                        (exp for exp in expected if isinstance(exp, dict)), None246                    )247                    if (expected_list, expected_dict) != (None, None):248                        self.assertParseResultsEquals(249                            result,250                            expected_list=expected_list,251                            expected_dict=expected_dict,252                            msg=fail_msg or msg,253                        )254                    else:255                        # warning here maybe?256                        print(f"no validation for {test_string!r}")257 258            # do this last, in case some specific test results can be reported instead259            self.assertTrue(260                run_test_success, msg=msg if msg is not None else "failed runTests"261            )262 263        @contextmanager264        def assertRaisesParseException(265            self, exc_type=ParseException, expected_msg=None, msg=None266        ):267            if expected_msg is not None:268                if isinstance(expected_msg, str):269                    expected_msg = re.escape(expected_msg)270                with self.assertRaisesRegex(exc_type, expected_msg, msg=msg) as ctx:271                    yield ctx272 273            else:274                with self.assertRaises(exc_type, msg=msg) as ctx:275                    yield ctx276 277    @staticmethod278    def with_line_numbers(279        s: str,280        start_line: typing.Optional[int] = None,281        end_line: typing.Optional[int] = None,282        expand_tabs: bool = True,283        eol_mark: str = "|",284        mark_spaces: typing.Optional[str] = None,285        mark_control: typing.Optional[str] = None,286        *,287        indent: typing.Union[str, int] = "",288        base_1: bool = True,289    ) -> str:290        """291        Helpful method for debugging a parser - prints a string with line and column numbers.292        (Line and column numbers are 1-based by default - if debugging a parse action,293        pass base_1=False, to correspond to the loc value passed to the parse action.)294 295        :param s: string to be printed with line and column numbers296        :param start_line: starting line number in s to print (default=1)297        :param end_line: ending line number in s to print (default=len(s))298        :param expand_tabs: expand tabs to spaces, to match the pyparsing default299        :param eol_mark: string to mark the end of lines, helps visualize trailing spaces300        :param mark_spaces: special character to display in place of spaces301        :param mark_control: convert non-printing control characters to a placeholding302                             character; valid values:303 304                             - ``"unicode"`` - replaces control chars with Unicode symbols, such as "␍" and "␊"305                             - any single character string - replace control characters with given string306                             - ``None`` (default) - string is displayed as-is307 308 309        :param indent: string to indent with line and column numbers; if an int310                       is passed, converted to ``" " * indent``311        :param base_1: whether to label string using base 1; if False, string will be312                       labeled based at 0313 314        :returns: input string with leading line numbers and column number headers315 316        .. versionchanged:: 3.2.0317           New ``indent`` and ``base_1`` arguments.318        """319        if expand_tabs:320            s = s.expandtabs()321        if isinstance(indent, int):322            indent = " " * indent323        indent = indent.expandtabs()324        if mark_control is not None:325            mark_control = typing.cast(str, mark_control)326            if mark_control == "unicode":327                transtable_map = {328                    c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))329                }330                transtable_map[127] = 0x2421331                tbl = str.maketrans(transtable_map)332                eol_mark = ""333            else:334                ord_mark_control = ord(mark_control)335                tbl = str.maketrans(336                    {c: ord_mark_control for c in list(range(0, 32)) + [127]}337                )338            s = s.translate(tbl)339        if mark_spaces is not None and mark_spaces != " ":340            if mark_spaces == "unicode":341                tbl = str.maketrans({9: 0x2409, 32: 0x2423})342                s = s.translate(tbl)343            else:344                s = s.replace(" ", mark_spaces)345        if start_line is None:346            start_line = 0347        if end_line is None:348            end_line = len(s.splitlines())349        end_line = min(end_line, len(s.splitlines()))350        start_line = min(max(0, start_line), end_line)351 352        if mark_control != "unicode":353            s_lines = s.splitlines()[max(start_line - base_1, 0) : end_line]354        else:355            s_lines = [356                line + "␊"357                for line in s.split("␊")[max(start_line - base_1, 0) : end_line]358            ]359        if not s_lines:360            return ""361 362        lineno_width = len(str(end_line))363        max_line_len = max(len(line) for line in s_lines)364        lead = indent + " " * (lineno_width + 1)365 366        if max_line_len >= 99:367            header0 = (368                lead369                + ("" if base_1 else " ")370                + "".join(371                    f"{' ' * 99}{(i + 1) % 100}"372                    for i in range(max(max_line_len // 100, 1))373                )374                + "\n"375            )376        else:377            header0 = ""378 379        header1 = (380            ("" if base_1 else " ")381            + lead382            + "".join(f"         {(i + 1) % 10}" for i in range(-(-max_line_len // 10)))383            + "\n"384        )385        digits = "1234567890"386        header2 = (387            lead + ("" if base_1 else "0") + digits * (-(-max_line_len // 10)) + "\n"388        )389        return (390            header0391            + header1392            + header2393            + "\n".join(394                f"{indent}{i:{lineno_width}d}:{line}{eol_mark}"395                for i, line in enumerate(s_lines, start=start_line + base_1)396            )397            + "\n"398        )399 
Aluode/PerceptionLabPortable · CoolFace