]> jfr.im git - dlqueue.git/blame - venv/lib/python3.11/site-packages/pip/_vendor/pyparsing/testing.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _vendor / pyparsing / testing.py
CommitLineData
e0df8241
JR
1# testing.py
2
3from contextlib import contextmanager
4import typing
5
6from .core import (
7 ParserElement,
8 ParseException,
9 Keyword,
10 __diag__,
11 __compat__,
12)
13
14
15class pyparsing_test:
16 """
17 namespace class for classes useful in writing unit tests
18 """
19
20 class reset_pyparsing_context:
21 """
22 Context manager to be used when writing unit tests that modify pyparsing config values:
23 - packrat parsing
24 - bounded recursion parsing
25 - default whitespace characters.
26 - default keyword characters
27 - literal string auto-conversion class
28 - __diag__ settings
29
30 Example::
31
32 with reset_pyparsing_context():
33 # test that literals used to construct a grammar are automatically suppressed
34 ParserElement.inlineLiteralsUsing(Suppress)
35
36 term = Word(alphas) | Word(nums)
37 group = Group('(' + term[...] + ')')
38
39 # assert that the '()' characters are not included in the parsed tokens
40 self.assertParseAndCheckList(group, "(abc 123 def)", ['abc', '123', 'def'])
41
42 # after exiting context manager, literals are converted to Literal expressions again
43 """
44
45 def __init__(self):
46 self._save_context = {}
47
48 def save(self):
49 self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS
50 self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS
51
52 self._save_context[
53 "literal_string_class"
54 ] = ParserElement._literalStringClass
55
56 self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace
57
58 self._save_context["packrat_enabled"] = ParserElement._packratEnabled
59 if ParserElement._packratEnabled:
60 self._save_context[
61 "packrat_cache_size"
62 ] = ParserElement.packrat_cache.size
63 else:
64 self._save_context["packrat_cache_size"] = None
65 self._save_context["packrat_parse"] = ParserElement._parse
66 self._save_context[
67 "recursion_enabled"
68 ] = ParserElement._left_recursion_enabled
69
70 self._save_context["__diag__"] = {
71 name: getattr(__diag__, name) for name in __diag__._all_names
72 }
73
74 self._save_context["__compat__"] = {
75 "collect_all_And_tokens": __compat__.collect_all_And_tokens
76 }
77
78 return self
79
80 def restore(self):
81 # reset pyparsing global state
82 if (
83 ParserElement.DEFAULT_WHITE_CHARS
84 != self._save_context["default_whitespace"]
85 ):
86 ParserElement.set_default_whitespace_chars(
87 self._save_context["default_whitespace"]
88 )
89
90 ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"]
91
92 Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"]
93 ParserElement.inlineLiteralsUsing(
94 self._save_context["literal_string_class"]
95 )
96
97 for name, value in self._save_context["__diag__"].items():
98 (__diag__.enable if value else __diag__.disable)(name)
99
100 ParserElement._packratEnabled = False
101 if self._save_context["packrat_enabled"]:
102 ParserElement.enable_packrat(self._save_context["packrat_cache_size"])
103 else:
104 ParserElement._parse = self._save_context["packrat_parse"]
105 ParserElement._left_recursion_enabled = self._save_context[
106 "recursion_enabled"
107 ]
108
109 __compat__.collect_all_And_tokens = self._save_context["__compat__"]
110
111 return self
112
113 def copy(self):
114 ret = type(self)()
115 ret._save_context.update(self._save_context)
116 return ret
117
118 def __enter__(self):
119 return self.save()
120
121 def __exit__(self, *args):
122 self.restore()
123
124 class TestParseResultsAsserts:
125 """
126 A mixin class to add parse results assertion methods to normal unittest.TestCase classes.
127 """
128
129 def assertParseResultsEquals(
130 self, result, expected_list=None, expected_dict=None, msg=None
131 ):
132 """
133 Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``,
134 and compare any defined results names with an optional ``expected_dict``.
135 """
136 if expected_list is not None:
137 self.assertEqual(expected_list, result.as_list(), msg=msg)
138 if expected_dict is not None:
139 self.assertEqual(expected_dict, result.as_dict(), msg=msg)
140
141 def assertParseAndCheckList(
142 self, expr, test_string, expected_list, msg=None, verbose=True
143 ):
144 """
145 Convenience wrapper assert to test a parser element and input string, and assert that
146 the resulting ``ParseResults.asList()`` is equal to the ``expected_list``.
147 """
148 result = expr.parse_string(test_string, parse_all=True)
149 if verbose:
150 print(result.dump())
151 else:
152 print(result.as_list())
153 self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg)
154
155 def assertParseAndCheckDict(
156 self, expr, test_string, expected_dict, msg=None, verbose=True
157 ):
158 """
159 Convenience wrapper assert to test a parser element and input string, and assert that
160 the resulting ``ParseResults.asDict()`` is equal to the ``expected_dict``.
161 """
162 result = expr.parse_string(test_string, parseAll=True)
163 if verbose:
164 print(result.dump())
165 else:
166 print(result.as_list())
167 self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg)
168
169 def assertRunTestResults(
170 self, run_tests_report, expected_parse_results=None, msg=None
171 ):
172 """
173 Unit test assertion to evaluate output of ``ParserElement.runTests()``. If a list of
174 list-dict tuples is given as the ``expected_parse_results`` argument, then these are zipped
175 with the report tuples returned by ``runTests`` and evaluated using ``assertParseResultsEquals``.
176 Finally, asserts that the overall ``runTests()`` success value is ``True``.
177
178 :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests
179 :param expected_parse_results (optional): [tuple(str, list, dict, Exception)]
180 """
181 run_test_success, run_test_results = run_tests_report
182
183 if expected_parse_results is not None:
184 merged = [
185 (*rpt, expected)
186 for rpt, expected in zip(run_test_results, expected_parse_results)
187 ]
188 for test_string, result, expected in merged:
189 # expected should be a tuple containing a list and/or a dict or an exception,
190 # and optional failure message string
191 # an empty tuple will skip any result validation
192 fail_msg = next(
193 (exp for exp in expected if isinstance(exp, str)), None
194 )
195 expected_exception = next(
196 (
197 exp
198 for exp in expected
199 if isinstance(exp, type) and issubclass(exp, Exception)
200 ),
201 None,
202 )
203 if expected_exception is not None:
204 with self.assertRaises(
205 expected_exception=expected_exception, msg=fail_msg or msg
206 ):
207 if isinstance(result, Exception):
208 raise result
209 else:
210 expected_list = next(
211 (exp for exp in expected if isinstance(exp, list)), None
212 )
213 expected_dict = next(
214 (exp for exp in expected if isinstance(exp, dict)), None
215 )
216 if (expected_list, expected_dict) != (None, None):
217 self.assertParseResultsEquals(
218 result,
219 expected_list=expected_list,
220 expected_dict=expected_dict,
221 msg=fail_msg or msg,
222 )
223 else:
224 # warning here maybe?
225 print(f"no validation for {test_string!r}")
226
227 # do this last, in case some specific test results can be reported instead
228 self.assertTrue(
229 run_test_success, msg=msg if msg is not None else "failed runTests"
230 )
231
232 @contextmanager
233 def assertRaisesParseException(self, exc_type=ParseException, msg=None):
234 with self.assertRaises(exc_type, msg=msg):
235 yield
236
237 @staticmethod
238 def with_line_numbers(
239 s: str,
240 start_line: typing.Optional[int] = None,
241 end_line: typing.Optional[int] = None,
242 expand_tabs: bool = True,
243 eol_mark: str = "|",
244 mark_spaces: typing.Optional[str] = None,
245 mark_control: typing.Optional[str] = None,
246 ) -> str:
247 """
248 Helpful method for debugging a parser - prints a string with line and column numbers.
249 (Line and column numbers are 1-based.)
250
251 :param s: tuple(bool, str - string to be printed with line and column numbers
252 :param start_line: int - (optional) starting line number in s to print (default=1)
253 :param end_line: int - (optional) ending line number in s to print (default=len(s))
254 :param expand_tabs: bool - (optional) expand tabs to spaces, to match the pyparsing default
255 :param eol_mark: str - (optional) string to mark the end of lines, helps visualize trailing spaces (default="|")
256 :param mark_spaces: str - (optional) special character to display in place of spaces
257 :param mark_control: str - (optional) convert non-printing control characters to a placeholding
258 character; valid values:
259 - "unicode" - replaces control chars with Unicode symbols, such as "␍" and "␊"
260 - any single character string - replace control characters with given string
261 - None (default) - string is displayed as-is
262
263 :return: str - input string with leading line numbers and column number headers
264 """
265 if expand_tabs:
266 s = s.expandtabs()
267 if mark_control is not None:
268 mark_control = typing.cast(str, mark_control)
269 if mark_control == "unicode":
270 transtable_map = {
271 c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))
272 }
273 transtable_map[127] = 0x2421
274 tbl = str.maketrans(transtable_map)
275 eol_mark = ""
276 else:
277 ord_mark_control = ord(mark_control)
278 tbl = str.maketrans(
279 {c: ord_mark_control for c in list(range(0, 32)) + [127]}
280 )
281 s = s.translate(tbl)
282 if mark_spaces is not None and mark_spaces != " ":
283 if mark_spaces == "unicode":
284 tbl = str.maketrans({9: 0x2409, 32: 0x2423})
285 s = s.translate(tbl)
286 else:
287 s = s.replace(" ", mark_spaces)
288 if start_line is None:
289 start_line = 1
290 if end_line is None:
291 end_line = len(s)
292 end_line = min(end_line, len(s))
293 start_line = min(max(1, start_line), end_line)
294
295 if mark_control != "unicode":
296 s_lines = s.splitlines()[start_line - 1 : end_line]
297 else:
298 s_lines = [line + "␊" for line in s.split("␊")[start_line - 1 : end_line]]
299 if not s_lines:
300 return ""
301
302 lineno_width = len(str(end_line))
303 max_line_len = max(len(line) for line in s_lines)
304 lead = " " * (lineno_width + 1)
305 if max_line_len >= 99:
306 header0 = (
307 lead
308 + "".join(
309 f"{' ' * 99}{(i + 1) % 100}"
310 for i in range(max(max_line_len // 100, 1))
311 )
312 + "\n"
313 )
314 else:
315 header0 = ""
316 header1 = (
317 header0
318 + lead
319 + "".join(f" {(i + 1) % 10}" for i in range(-(-max_line_len // 10)))
320 + "\n"
321 )
322 header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n"
323 return (
324 header1
325 + header2
326 + "\n".join(
327 f"{i:{lineno_width}d}:{line}{eol_mark}"
328 for i, line in enumerate(s_lines, start=start_line)
329 )
330 + "\n"
331 )