diff options
| author | natsuoto <[email protected]> | 2026-07-15 15:06:01 +0530 |
|---|---|---|
| committer | natsuoto <[email protected]> | 2026-07-15 15:06:01 +0530 |
| commit | badc9afaf1d761910ccfdef644e9b7dd31ad0ece (patch) | |
| tree | ea0a28eb9bf2442bd91503db52a498cca51057b0 | |
| parent | 45baa7584f767213ac4dcf2b0c02e600329b03d2 (diff) | |
| download | edify-badc9afaf1d761910ccfdef644e9b7dd31ad0ece.tar.xz edify-badc9afaf1d761910ccfdef644e9b7dd31ad0ece.zip | |
test: snapshot deeply-nested alternation, combined lookaround, named-backref, and ReDoS edge fixtures
10 files changed, 135 insertions, 3 deletions
diff --git a/tests/docs/snapshots.test.py b/tests/docs/snapshots.test.py index 031a382..f734452 100644 --- a/tests/docs/snapshots.test.py +++ b/tests/docs/snapshots.test.py @@ -8,12 +8,11 @@ change surfaces every doc example the change touches so their prose stays truthful. """ +import re from pathlib import Path import pytest -import re - import edify import edify.library as edify_library from edify import Pattern @@ -103,7 +102,9 @@ _BLOCKS_DEFERRED_TO_DOCS_REWRITE = frozenset( @pytest.mark.parametrize( ("rst_path", "block_start", "block_source", "relative_stem"), DISCOVERED_BLOCKS, - ids=[f"{relative_stem}:{block_start}" for _, block_start, _, relative_stem in DISCOVERED_BLOCKS], + ids=[ + f"{relative_stem}:{block_start}" for _, block_start, _, relative_stem in DISCOVERED_BLOCKS + ], ) def test_doc_code_block_produces_the_snapshotted_regex( rst_path, block_start, block_source, relative_stem diff --git a/tests/fixtures/edge_cases.test.py b/tests/fixtures/edge_cases.test.py new file mode 100644 index 0000000..4e74983 --- /dev/null +++ b/tests/fixtures/edge_cases.test.py @@ -0,0 +1,123 @@ +"""Snapshot suite for the compile-path edge-case fixtures. + +Each fixture builds a small, intentionally-tricky pattern and pins the +emitted regex string. The fixtures are the ones most likely to silently +drift when the compile path is refactored: deeply nested alternation, +combined lookaround, item-4 named backreferences, and constructs that +:mod:`edify` flags for ReDoS. +""" + +from pathlib import Path + +import pytest + +from edify import Pattern, RegexBuilder +from edify.testing import assert_snapshot + +_SNAPSHOT_ROOT = Path(__file__).parent.parent / "snapshots" / "fixtures" + + +def _deeply_nested_alternation(): + inner_alt = RegexBuilder().any_of("abc", "def", "ghi") + outer_alt = RegexBuilder().any_of("first", "second", "third") + return ( + RegexBuilder() + .start_of_input() + .subexpression(inner_alt) + .string("-") + .subexpression(outer_alt) + .end_of_input() + ) + + +def _combined_lookaround_positive_and_negative(): + return ( + RegexBuilder() + .assert_ahead() + .digit() + .end() + .assert_not_ahead() + .string("0") + .end() + .assert_behind() + .string("$") + .end() + .digit() + .digit() + .digit() + ) + + +def _named_backreference_pair(): + return ( + RegexBuilder() + .named_capture("open") + .any_of("[", "(", "{") + .end() + .zero_or_more() + .word() + .named_back_reference("open") + ) + + +def _nested_unbounded_quantifier_redos(): + return RegexBuilder().one_or_more().group().one_or_more().digit().end() + + +def _alternation_with_overlapping_prefixes_redos(): + return RegexBuilder().one_or_more().any_of("aa", "aaa", "aaaa") + + +def _hex_color_pattern(): + return ( + RegexBuilder() + .start_of_input() + .string("#") + .any_of_chars("0123456789abcdefABCDEF") + .exactly(6) + .word() + .end_of_input() + ) + + +def _email_shape_pattern(): + return ( + RegexBuilder() + .start_of_input() + .one_or_more() + .any_of_chars("a-zA-Z0-9._%+-") + .string("@") + .one_or_more() + .any_of_chars("a-zA-Z0-9.-") + .string(".") + .at_least(2) + .letter() + .end_of_input() + ) + + +def _pattern_composition_via_use(): + reusable = Pattern().at_least(2).letter() + return RegexBuilder().start_of_input().subexpression(reusable).end_of_input() + + +_FIXTURES = [ + ("deeply_nested_alternation", _deeply_nested_alternation), + ("combined_lookaround", _combined_lookaround_positive_and_negative), + ("named_backreference_pair", _named_backreference_pair), + ("nested_unbounded_quantifier_redos", _nested_unbounded_quantifier_redos), + ("alternation_with_overlapping_prefixes_redos", _alternation_with_overlapping_prefixes_redos), + ("hex_color_pattern", _hex_color_pattern), + ("email_shape_pattern", _email_shape_pattern), + ("pattern_composition_via_use", _pattern_composition_via_use), +] + + + ("fixture_name", "builder_factory"), _FIXTURES, ids=[name for name, _ in _FIXTURES] +) +def test_edge_case_fixture_emits_the_snapshotted_regex(fixture_name, builder_factory): + builder = builder_factory() + emitted = builder.to_regex_string() + snapshot_path = _SNAPSHOT_ROOT / f"{fixture_name}.regex" + assert_snapshot(emitted, snapshot_path) diff --git a/tests/snapshots/fixtures/alternation_with_overlapping_prefixes_redos.regex b/tests/snapshots/fixtures/alternation_with_overlapping_prefixes_redos.regex new file mode 100644 index 0000000..8d3f7b5 --- /dev/null +++ b/tests/snapshots/fixtures/alternation_with_overlapping_prefixes_redos.regex @@ -0,0 +1 @@ +(?:aa|aaa|aaaa)+
\ No newline at end of file diff --git a/tests/snapshots/fixtures/combined_lookaround.regex b/tests/snapshots/fixtures/combined_lookaround.regex new file mode 100644 index 0000000..baa7e8f --- /dev/null +++ b/tests/snapshots/fixtures/combined_lookaround.regex @@ -0,0 +1 @@ +(?=\d)(?!0)(?<=\$)\d\d\d
\ No newline at end of file diff --git a/tests/snapshots/fixtures/deeply_nested_alternation.regex b/tests/snapshots/fixtures/deeply_nested_alternation.regex new file mode 100644 index 0000000..0f2c26b --- /dev/null +++ b/tests/snapshots/fixtures/deeply_nested_alternation.regex @@ -0,0 +1 @@ +^(?:abc|def|ghi)\-(?:first|second|third)$
\ No newline at end of file diff --git a/tests/snapshots/fixtures/email_shape_pattern.regex b/tests/snapshots/fixtures/email_shape_pattern.regex new file mode 100644 index 0000000..bd17d4d --- /dev/null +++ b/tests/snapshots/fixtures/email_shape_pattern.regex @@ -0,0 +1 @@ +^[a\-zA\-Z0\-9._%+-]+@[a\-zA\-Z0\-9.-]+\.[a-zA-Z]{2,}$
\ No newline at end of file diff --git a/tests/snapshots/fixtures/hex_color_pattern.regex b/tests/snapshots/fixtures/hex_color_pattern.regex new file mode 100644 index 0000000..c2d63c0 --- /dev/null +++ b/tests/snapshots/fixtures/hex_color_pattern.regex @@ -0,0 +1 @@ +^\#[0123456789abcdefABCDEF]\w{6}$
\ No newline at end of file diff --git a/tests/snapshots/fixtures/named_backreference_pair.regex b/tests/snapshots/fixtures/named_backreference_pair.regex new file mode 100644 index 0000000..1e3fe4a --- /dev/null +++ b/tests/snapshots/fixtures/named_backreference_pair.regex @@ -0,0 +1 @@ +(?P<open>[\[\(\{])\w*(?P=open)
\ No newline at end of file diff --git a/tests/snapshots/fixtures/nested_unbounded_quantifier_redos.regex b/tests/snapshots/fixtures/nested_unbounded_quantifier_redos.regex new file mode 100644 index 0000000..e5aaf8b --- /dev/null +++ b/tests/snapshots/fixtures/nested_unbounded_quantifier_redos.regex @@ -0,0 +1 @@ +(?:\d+)+
\ No newline at end of file diff --git a/tests/snapshots/fixtures/pattern_composition_via_use.regex b/tests/snapshots/fixtures/pattern_composition_via_use.regex new file mode 100644 index 0000000..70b3c7c --- /dev/null +++ b/tests/snapshots/fixtures/pattern_composition_via_use.regex @@ -0,0 +1 @@ +^[a-zA-Z]{2,}$
\ No newline at end of file |
