aboutsummaryrefslogtreecommitdiff
path: root/tests/builder/reverse.test.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/builder/reverse.test.py')
-rw-r--r--tests/builder/reverse.test.py113
1 files changed, 113 insertions, 0 deletions
diff --git a/tests/builder/reverse.test.py b/tests/builder/reverse.test.py
new file mode 100644
index 0000000..1ed00ec
--- /dev/null
+++ b/tests/builder/reverse.test.py
@@ -0,0 +1,113 @@
+"""Tests for :meth:`RegexBuilder.from_regex` — the reverse parser."""
+
+import re
+
+import pytest
+
+from edify import RegexBuilder
+from edify.builder.reverse import UnsupportedReverseParseError
+
+
+ ("source", "expected"),
+ [
+ ("hello", "hello"),
+ ("a", "a"),
+ (r"\d+", r"\d+"),
+ (r"\d*", r"\d*"),
+ (r"\d?", r"\d?"),
+ (r"\d{3}", r"\d{3}"),
+ (r"\d{2,5}", r"\d{2,5}"),
+ (r"\d{2,}", r"\d{2,}"),
+ (r"\w", r"\w"),
+ (r"\W", r"\W"),
+ (r"\s", r"\s"),
+ (r"\S", r"\S"),
+ (r"\D", r"\D"),
+ ("[a-z]", "[a-z]"),
+ ("[abc]", "[abc]"),
+ ("^", "^"),
+ ("$", "$"),
+ (r"\b", r"\b"),
+ (r"\B", r"\B"),
+ (".", "."),
+ ("(?:foo)", "foo"),
+ ("(abc)", "(abc)"),
+ ("(?P<name>abc)", "(?P<name>abc)"),
+ ("foo|bar", "(?:foo|bar)"),
+ ("(?=x)y", "(?=x)y"),
+ ("(?!x)y", "(?!x)y"),
+ ("(?<=x)y", "(?<=x)y"),
+ ("(?<!x)y", "(?<!x)y"),
+ ],
+)
+def test_from_regex_translates_common_constructs_faithfully(source, expected):
+ builder = RegexBuilder.from_regex(source)
+ assert builder.to_regex_string() == expected
+
+
+ "source",
+ [
+ "hello",
+ r"\d+",
+ "^abc$",
+ r"[a-z]+",
+ "(?P<x>abc)",
+ "foo|bar",
+ r"(?=x)\w+",
+ ],
+)
+def test_round_trip_compiled_regex_matches_the_same_inputs(source):
+ original = re.compile(source)
+ reversed_builder = RegexBuilder.from_regex(source)
+ reversed_compiled = reversed_builder.to_regex()
+ for candidate in ["", "a", "abc", "abc123", "xyz", "foo bar", "hi"]:
+ assert bool(original.search(candidate)) == bool(reversed_compiled.search(candidate))
+
+
+def test_from_regex_raises_for_unsupported_construct():
+ with pytest.raises(UnsupportedReverseParseError):
+ RegexBuilder.from_regex(r"(a)\1")
+
+
+def test_from_regex_raises_for_alternation_with_non_literal_branch():
+ with pytest.raises(UnsupportedReverseParseError):
+ RegexBuilder.from_regex(r"a|\d+")
+
+
+def test_from_regex_raises_for_unsupported_class_member_shape():
+ with pytest.raises(UnsupportedReverseParseError):
+ RegexBuilder.from_regex(r"[\da-z_]")
+
+
+def test_from_regex_lazy_quantifier_translates_via_lazy_variants():
+ builder = RegexBuilder.from_regex(r"a+?")
+ assert builder.to_regex_string() == "a+?"
+
+
+def test_from_regex_lazy_between_translates_via_lazy_variant():
+ builder = RegexBuilder.from_regex(r"a{2,5}?")
+ assert builder.to_regex_string() == "a{2,5}?"
+
+
+def test_from_regex_lazy_zero_or_more_translates_via_lazy_variant():
+ builder = RegexBuilder.from_regex(r"a*?")
+ assert builder.to_regex_string() == "a*?"
+
+
+def test_from_regex_named_group_reproduces_the_name():
+ builder = RegexBuilder.from_regex("(?P<username>[a-z]+)")
+ emitted = builder.to_regex_string()
+ assert emitted == "(?P<username>[a-z]+)"
+
+
+def test_unsupported_error_message_names_the_offending_construct():
+ with pytest.raises(UnsupportedReverseParseError) as excinfo:
+ RegexBuilder.from_regex(r"(a)\1")
+ assert "hand-write" in str(excinfo.value)
+
+
+def test_from_regex_produces_a_regexbuilder_not_a_pattern():
+ builder = RegexBuilder.from_regex("abc")
+ assert isinstance(builder, RegexBuilder)