aboutsummaryrefslogtreecommitdiff
path: root/tests/builder
diff options
context:
space:
mode:
Diffstat (limited to 'tests/builder')
-rw-r--r--tests/builder/cache.test.py58
-rw-r--r--tests/builder/cold_warm.test.py69
-rw-r--r--tests/builder/flags.test.py31
-rw-r--r--tests/builder/lookbehind.test.py6
-rw-r--r--tests/builder/ordering.test.py (renamed from tests/builder/insertion_order.test.py)0
-rw-r--r--tests/builder/passthrough.test.py7
-rw-r--r--tests/builder/validation.test.py8
7 files changed, 84 insertions, 95 deletions
diff --git a/tests/builder/cache.test.py b/tests/builder/cache.test.py
index 700a8e4..045e8e9 100644
--- a/tests/builder/cache.test.py
+++ b/tests/builder/cache.test.py
@@ -1,8 +1,33 @@
-"""Tests for the per-instance lazy compile cache."""
+"""Tests for the per-instance lazy compile cache — semantics and cold/warm ratio."""
+
+import time
from edify import Pattern, RegexBuilder
+def _hex_number_builder():
+ return (
+ RegexBuilder()
+ .start_of_input()
+ .assert_ahead()
+ .any_of("0x", "0o", "0b")
+ .end()
+ .capture()
+ .any_of("0x", "0o", "0b")
+ .end()
+ .between(1, 16)
+ .any_of_chars("0123456789abcdefABCDEF")
+ .end_of_input()
+ )
+
+
+def _measure_call_wall_clock(action, iterations):
+ start = time.perf_counter()
+ for _ in range(iterations):
+ action()
+ return time.perf_counter() - start
+
+
def test_two_no_kwargs_to_regex_calls_return_the_same_instance():
builder = RegexBuilder().one_or_more().digit()
first = builder.to_regex()
@@ -61,3 +86,34 @@ def test_chain_step_yields_a_fresh_cache_slot():
def test_pattern_also_caches_across_repeat_to_regex_calls():
pattern = Pattern().string("hi")
assert pattern.to_regex() is pattern.to_regex()
+
+
+def test_warm_to_regex_is_at_least_ten_times_cheaper_than_cold_to_regex():
+ warm_iterations = 200
+ cold_iterations = 200
+
+ cold_total = 0.0
+ for _ in range(cold_iterations):
+ builder = _hex_number_builder()
+ cold_start = time.perf_counter()
+ builder.to_regex()
+ cold_total += time.perf_counter() - cold_start
+
+ warm_builder = _hex_number_builder()
+ warm_builder.to_regex()
+ warm_total = _measure_call_wall_clock(warm_builder.to_regex, warm_iterations)
+
+ average_cold = cold_total / cold_iterations
+ average_warm = warm_total / warm_iterations
+
+ assert average_warm * 10 < average_cold, (
+ f"cache ratio too weak: warm={average_warm * 1e6:.2f}µs, "
+ f"cold={average_cold * 1e6:.2f}µs — warm should be < cold / 10."
+ )
+
+
+def test_repeat_to_regex_returns_the_same_regex_instance():
+ warm_builder = _hex_number_builder()
+ first = warm_builder.to_regex()
+ second = warm_builder.to_regex()
+ assert first is second
diff --git a/tests/builder/cold_warm.test.py b/tests/builder/cold_warm.test.py
deleted file mode 100644
index 8777f90..0000000
--- a/tests/builder/cold_warm.test.py
+++ /dev/null
@@ -1,69 +0,0 @@
-"""Deterministic cold/warm ratio gate for the per-instance lazy compile cache.
-
-Measures the wall-clock cost of the first ``.to_regex()`` call on a fresh
-builder (``cold``) against the cost of a repeat call on the same instance
-(``warm``). The cache promise from :issue:`141` is that the warm path is
-an order of magnitude cheaper than the cold path — a hardware-independent
-ratio that gates the cache contract in a way absolute milliseconds cannot.
-"""
-
-from __future__ import annotations
-
-import time
-
-from edify import RegexBuilder
-
-
-def _hex_number_builder():
- builder = (
- RegexBuilder()
- .start_of_input()
- .assert_ahead()
- .any_of("0x", "0o", "0b")
- .end()
- .capture()
- .any_of("0x", "0o", "0b")
- .end()
- .between(1, 16)
- .any_of_chars("0123456789abcdefABCDEF")
- .end_of_input()
- )
- return builder
-
-
-def _measure_call_wall_clock(action, iterations):
- start = time.perf_counter()
- for _ in range(iterations):
- action()
- return time.perf_counter() - start
-
-
-def test_warm_to_regex_is_at_least_ten_times_cheaper_than_cold_to_regex():
- warm_iterations = 200
- cold_iterations = 200
-
- cold_total = 0.0
- for _ in range(cold_iterations):
- builder = _hex_number_builder()
- cold_start = time.perf_counter()
- builder.to_regex()
- cold_total += time.perf_counter() - cold_start
-
- warm_builder = _hex_number_builder()
- warm_builder.to_regex()
- warm_total = _measure_call_wall_clock(warm_builder.to_regex, warm_iterations)
-
- average_cold = cold_total / cold_iterations
- average_warm = warm_total / warm_iterations
-
- assert average_warm * 10 < average_cold, (
- f"cache ratio too weak: warm={average_warm * 1e6:.2f}µs, "
- f"cold={average_cold * 1e6:.2f}µs — warm should be < cold / 10."
- )
-
-
-def test_repeat_to_regex_returns_the_same_regex_instance():
- warm_builder = _hex_number_builder()
- first = warm_builder.to_regex()
- second = warm_builder.to_regex()
- assert first is second
diff --git a/tests/builder/flags.test.py b/tests/builder/flags.test.py
index 02e875e..704065f 100644
--- a/tests/builder/flags.test.py
+++ b/tests/builder/flags.test.py
@@ -4,6 +4,7 @@ import re
import sys
import pytest
+import regex as regex_module
from edify import Pattern, RegexBuilder
@@ -72,38 +73,32 @@ def test_multiple_kwargs_combine():
assert compiled.compiled.flags & re.S == re.S
-def _regex_module():
- import regex
-
- return regex
-
-
def test_regex_engine_ignore_case_kwarg_enables_the_flag():
- regex = _regex_module()
+ regex = regex_module
compiled = RegexBuilder().string("hi").to_regex(engine="regex", ignore_case=True)
assert compiled.compiled.flags & regex.I == regex.I
def test_regex_engine_multiline_kwarg_enables_the_flag():
- regex = _regex_module()
+ regex = regex_module
compiled = RegexBuilder().string("hi").to_regex(engine="regex", multiline=True)
assert compiled.compiled.flags & regex.M == regex.M
def test_regex_engine_dotall_kwarg_enables_the_flag():
- regex = _regex_module()
+ regex = regex_module
compiled = RegexBuilder().string("hi").to_regex(engine="regex", dotall=True)
assert compiled.compiled.flags & regex.S == regex.S
def test_regex_engine_ascii_only_kwarg_enables_the_flag():
- regex = _regex_module()
+ regex = regex_module
compiled = RegexBuilder().string("hi").to_regex(engine="regex", ascii_only=True)
assert compiled.compiled.flags & regex.A == regex.A
def test_regex_engine_verbose_kwarg_enables_the_flag():
- regex = _regex_module()
+ regex = regex_module
compiled = RegexBuilder().string("hi").to_regex(engine="regex", verbose=True)
assert compiled.compiled.flags & regex.X == regex.X
@@ -115,3 +110,17 @@ def test_regex_engine_verbose_kwarg_enables_the_flag():
def test_regex_engine_debug_kwarg_compiles_without_error():
compiled = RegexBuilder().string("hi").to_regex(engine="regex", debug=True)
assert compiled.source == "hi"
+
+
+def test_regex_engine_debug_flag_is_forwarded_to_regex_module_bitmask(monkeypatch):
+ captured_flags: list[int] = []
+ original_compile = regex_module.compile
+
+ def capturing_compile(pattern, flags=0):
+ captured_flags.append(flags)
+ return original_compile(pattern)
+
+ monkeypatch.setattr(regex_module, "compile", capturing_compile)
+ RegexBuilder().string("hi").to_regex(engine="regex", debug=True)
+ assert captured_flags
+ assert captured_flags[0] & regex_module.DEBUG == regex_module.DEBUG
diff --git a/tests/builder/lookbehind.test.py b/tests/builder/lookbehind.test.py
index f16a6cc..5e101f3 100644
--- a/tests/builder/lookbehind.test.py
+++ b/tests/builder/lookbehind.test.py
@@ -1,5 +1,7 @@
"""Tests for lookbehind width behavior across the ``re`` and ``regex`` engines."""
+import re
+
import pytest
from edify import RegexBuilder
@@ -42,14 +44,10 @@ def test_fixed_width_lookbehind_still_works_under_re():
def test_variable_width_lookbehind_error_chains_the_underlying_pattern_error():
with pytest.raises(VariableWidthLookbehindNotSupportedError) as excinfo:
_variable_width_lookbehind_builder().to_regex(engine="re")
- import re
-
assert isinstance(excinfo.value.__cause__, re.error)
def test_re_engine_still_surfaces_other_pattern_errors_unchanged(monkeypatch):
- import re
-
def raise_other_error(_pattern, flags=0):
raise re.error("some other syntax error")
diff --git a/tests/builder/insertion_order.test.py b/tests/builder/ordering.test.py
index 4feae65..4feae65 100644
--- a/tests/builder/insertion_order.test.py
+++ b/tests/builder/ordering.test.py
diff --git a/tests/builder/passthrough.test.py b/tests/builder/passthrough.test.py
index a2e77b5..e149052 100644
--- a/tests/builder/passthrough.test.py
+++ b/tests/builder/passthrough.test.py
@@ -1,6 +1,9 @@
"""Tests for the pass-through branches when subexpression anchors merge without conflict."""
+import pytest
+
from edify import RegexBuilder
+from edify.errors.structure import CannotCallSubexpressionError
def test_start_of_input_merges_into_parent_without_existing_start():
@@ -18,10 +21,6 @@ def test_end_of_input_merges_into_parent_without_existing_end():
def test_subexpression_called_with_unfinished_expression_raises():
- import pytest
-
- from edify.errors.structure import CannotCallSubexpressionError
-
unfinished_sub = RegexBuilder().capture().digit()
parent = RegexBuilder()
with pytest.raises(CannotCallSubexpressionError):
diff --git a/tests/builder/validation.test.py b/tests/builder/validation.test.py
index 53128c5..a1b57d7 100644
--- a/tests/builder/validation.test.py
+++ b/tests/builder/validation.test.py
@@ -14,6 +14,7 @@ from edify.errors.anchors import (
)
from edify.errors.input import (
MustBeAStringError,
+ MustBeInstanceError,
MustBeIntegerGreaterThanZeroError,
MustBeLessThanError,
MustBeOneCharacterError,
@@ -22,6 +23,7 @@ from edify.errors.input import (
MustHaveASmallerValueError,
)
from edify.errors.naming import NamedGroupDoesNotExistError
+from edify.errors.structure import CannotCallSubexpressionError
def test_start_of_input_twice_raises():
@@ -140,23 +142,17 @@ def test_named_back_reference_undeclared_raises():
def test_to_regex_string_with_open_frame_raises():
- from edify.errors.structure import CannotCallSubexpressionError
-
unfinished = RegexBuilder().capture().digit()
with pytest.raises(CannotCallSubexpressionError):
unfinished.to_regex_string()
def test_to_regex_with_open_frame_raises():
- from edify.errors.structure import CannotCallSubexpressionError
-
unfinished = RegexBuilder().capture().digit()
with pytest.raises(CannotCallSubexpressionError):
unfinished.to_regex()
def test_subexpression_non_builder_raises():
- from edify.errors.input import MustBeInstanceError
-
with pytest.raises(MustBeInstanceError):
RegexBuilder().subexpression("not a builder")