blob: 97618bb9eace2f07039d01cdac80b91e6d0e05ae (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
"""Tests for the build-time ReDoS detector."""
import pytest
from edify import RegexBuilder
from edify.compile.redos import ReDoSWarning
def _classic_redos_builder():
return RegexBuilder().one_or_more().group().one_or_more().digit().end()
def test_classic_nested_unbounded_quantifier_triggers_the_warning():
with pytest.warns(ReDoSWarning, match="nested unbounded quantifier"):
_classic_redos_builder().to_regex()
def test_zero_or_more_wrapping_one_or_more_triggers_the_warning():
builder = RegexBuilder().zero_or_more().group().one_or_more().digit().end()
with pytest.warns(ReDoSWarning):
builder.to_regex()
def test_at_least_wrapping_zero_or_more_triggers_the_warning():
builder = RegexBuilder().at_least(2).group().zero_or_more().word().end()
with pytest.warns(ReDoSWarning):
builder.to_regex()
def test_lazy_variants_also_trigger_the_warning():
builder = RegexBuilder().one_or_more_lazy().group().zero_or_more_lazy().digit().end()
with pytest.warns(ReDoSWarning):
builder.to_regex()
def test_warning_message_names_both_quantifiers():
with pytest.warns(ReDoSWarning) as record:
_classic_redos_builder().to_regex()
message_text = str(record[0].message)
assert "one_or_more()" in message_text
assert "engine='regex'" in message_text
def test_bounded_quantifier_does_not_trigger_the_warning(recwarn: pytest.WarningsRecorder):
builder = RegexBuilder().exactly(3).group().exactly(2).digit().end()
builder.to_regex()
is_redos_flags = [isinstance(warning.message, ReDoSWarning) for warning in recwarn]
assert not any(is_redos_flags)
def test_grouped_quantifier_over_a_composite_group_does_not_trigger(
recwarn: pytest.WarningsRecorder,
):
builder = RegexBuilder().one_or_more().group().digit().letter().end()
builder.to_regex()
is_redos_flags = [isinstance(warning.message, ReDoSWarning) for warning in recwarn]
assert not any(is_redos_flags)
def test_grouped_quantifier_over_a_single_non_quantifier_child_does_not_trigger(
recwarn: pytest.WarningsRecorder,
):
builder = RegexBuilder().one_or_more().group().digit().end()
builder.to_regex()
is_redos_flags = [isinstance(warning.message, ReDoSWarning) for warning in recwarn]
assert not any(is_redos_flags)
def test_sequential_unbounded_quantifiers_do_not_trigger_the_warning(
recwarn: pytest.WarningsRecorder,
):
builder = RegexBuilder().one_or_more().digit().one_or_more().letter()
builder.to_regex()
is_redos_flags = [isinstance(warning.message, ReDoSWarning) for warning in recwarn]
assert not any(is_redos_flags)
def test_warning_only_fires_once_per_construct_within_a_single_terminal_call():
with pytest.warns(ReDoSWarning) as record:
_classic_redos_builder().to_regex()
redos_records = [w for w in record if isinstance(w.message, ReDoSWarning)]
assert len(redos_records) == 1
def test_terminal_still_returns_the_compiled_regex_after_warning():
with pytest.warns(ReDoSWarning):
compiled = _classic_redos_builder().to_regex()
assert compiled.source == "(?:\\d+)+"
|