Skip to content

Commit 7763fc2

Browse files
committed
Add tests
Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 5aad28d commit 7763fc2

File tree

3 files changed

+508
-0
lines changed

3 files changed

+508
-0
lines changed

tests/test_do_format.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the do_format() function."""
5+
6+
import dataclasses
7+
from typing import Iterator
8+
from unittest import mock
9+
10+
import pytest
11+
12+
from frequenz.pymdownx.superfences.filter_lines import (
13+
LinesRange,
14+
LinesRanges,
15+
Options,
16+
do_format,
17+
)
18+
19+
20+
@pytest.fixture
21+
def md_mock() -> Iterator[mock.MagicMock]:
22+
"""Mock the `md` object."""
23+
md = mock.MagicMock()
24+
preprocessor = mock.MagicMock()
25+
preprocessor.highlight.side_effect = True
26+
md.preprocessors.return_value = {"fenced_code_block": preprocessor}
27+
yield md
28+
29+
30+
_SOURCE = """\
31+
1. This is some text
32+
2. which have multiple lines
33+
3. and we want to filter some of them
34+
4. we number them
35+
5. so we can see which ones are filtered
36+
6. and which ones are not
37+
7. and we can also filter multiple ranges
38+
"""
39+
40+
41+
@dataclasses.dataclass(frozen=True, kw_only=True)
42+
class _TestCase:
43+
title: str | None = None
44+
options: Options = dataclasses.field(
45+
# We can't use just `Options` because `mypy` complains about type
46+
# incompatibility, which is not true.
47+
default_factory=lambda: Options() # pylint: disable=unnecessary-lambda
48+
)
49+
expected_src: str
50+
51+
52+
_cases = [
53+
_TestCase(title="No options", expected_src=_SOURCE),
54+
_TestCase(
55+
title="First line",
56+
options=Options(show_lines=LinesRanges({LinesRange(start=1, end=1)})),
57+
expected_src="""\
58+
1. This is some text
59+
""",
60+
),
61+
_TestCase(
62+
title="Middle line",
63+
options=Options(show_lines=LinesRanges({LinesRange(start=3, end=3)})),
64+
expected_src="""\
65+
3. and we want to filter some of them
66+
""",
67+
),
68+
_TestCase(
69+
title="Last line",
70+
options=Options(show_lines=LinesRanges({LinesRange(start=7, end=7)})),
71+
expected_src="""\
72+
7. and we can also filter multiple ranges
73+
""",
74+
),
75+
_TestCase(
76+
title="Open range from the start",
77+
options=Options(show_lines=LinesRanges({LinesRange(start=1)})),
78+
expected_src=_SOURCE,
79+
),
80+
_TestCase(
81+
title="Open range until the end",
82+
options=Options(show_lines=LinesRanges({LinesRange(end=7)})),
83+
expected_src=_SOURCE,
84+
),
85+
_TestCase(
86+
title="Open range with start in the middle",
87+
options=Options(show_lines=LinesRanges({LinesRange(start=3)})),
88+
expected_src="""\
89+
3. and we want to filter some of them
90+
4. we number them
91+
5. so we can see which ones are filtered
92+
6. and which ones are not
93+
7. and we can also filter multiple ranges
94+
""",
95+
),
96+
_TestCase(
97+
title="Open range with end in the middle",
98+
options=Options(show_lines=LinesRanges({LinesRange(end=3)})),
99+
expected_src="""\
100+
1. This is some text
101+
2. which have multiple lines
102+
3. and we want to filter some of them
103+
""",
104+
),
105+
# range with start and end in the middle
106+
_TestCase(
107+
title="Open range with start and end in the middle",
108+
options=Options(show_lines=LinesRanges({LinesRange(start=2, end=4)})),
109+
expected_src="""\
110+
2. which have multiple lines
111+
3. and we want to filter some of them
112+
4. we number them
113+
""",
114+
),
115+
_TestCase(
116+
title="Multiple lines",
117+
options=Options(
118+
show_lines=LinesRanges(
119+
{
120+
LinesRange(start=1, end=1),
121+
LinesRange(start=3, end=3),
122+
LinesRange(start=6, end=6),
123+
}
124+
)
125+
),
126+
expected_src="""\
127+
1. This is some text
128+
3. and we want to filter some of them
129+
6. and which ones are not
130+
""",
131+
),
132+
_TestCase(
133+
title="Multiple ranges",
134+
options=Options(
135+
show_lines=LinesRanges(
136+
{
137+
LinesRange(end=2),
138+
LinesRange(start=4, end=5),
139+
LinesRange(start=6),
140+
}
141+
)
142+
),
143+
expected_src="""\
144+
1. This is some text
145+
2. which have multiple lines
146+
4. we number them
147+
5. so we can see which ones are filtered
148+
6. and which ones are not
149+
7. and we can also filter multiple ranges
150+
""",
151+
),
152+
_TestCase(
153+
title="Multiple ranges with overlap",
154+
options=Options(
155+
show_lines=LinesRanges(
156+
{
157+
LinesRange(end=2),
158+
LinesRange(start=2, end=5),
159+
LinesRange(start=4, end=6),
160+
LinesRange(start=6),
161+
}
162+
)
163+
),
164+
expected_src=_SOURCE,
165+
),
166+
_TestCase(
167+
title="Multiple ranges and lines",
168+
options=Options(
169+
show_lines=LinesRanges(
170+
{
171+
LinesRange(end=1),
172+
LinesRange(start=4, end=4),
173+
LinesRange(start=5, end=5),
174+
LinesRange(start=7),
175+
}
176+
)
177+
),
178+
expected_src="""\
179+
1. This is some text
180+
4. we number them
181+
5. so we can see which ones are filtered
182+
7. and we can also filter multiple ranges
183+
""",
184+
),
185+
]
186+
187+
188+
@pytest.mark.parametrize(
189+
"case", _cases, ids=lambda c: f"{c.title} ({c.options.get('show_lines')})"
190+
)
191+
def test_do_format(
192+
case: _TestCase,
193+
md_mock: mock.MagicMock, # pylint: disable=redefined-outer-name
194+
) -> None:
195+
"""Test valid initializations succeed."""
196+
language = "xxx"
197+
class_name = "yyy"
198+
kwargs = {"kwarg": "test"}
199+
assert do_format(_SOURCE, language, class_name, case.options, md_mock, **kwargs)
200+
md_mock.preprocessors["fenced_code_block"].highlight.assert_called_once_with(
201+
src=case.expected_src,
202+
class_name=class_name,
203+
language=language,
204+
md=md_mock,
205+
options=case.options,
206+
**kwargs,
207+
)

tests/test_do_validate.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the do_validate() function."""
5+
6+
import dataclasses
7+
from typing import Iterator
8+
from unittest import mock
9+
10+
import markdown
11+
import pytest
12+
13+
from frequenz.pymdownx.superfences.filter_lines import (
14+
Inputs,
15+
LinesRange,
16+
LinesRanges,
17+
Options,
18+
do_validate,
19+
)
20+
21+
22+
@pytest.fixture
23+
def highlight_validator_mock() -> Iterator[mock.MagicMock]:
24+
"""Mock `highlight_validator`."""
25+
with mock.patch(
26+
"frequenz.pymdownx.superfences.filter_lines.highlight_validator",
27+
autospec=True,
28+
) as mock_highlight_validator:
29+
mock_highlight_validator.return_value = True
30+
yield mock_highlight_validator
31+
32+
33+
@dataclasses.dataclass(frozen=True, kw_only=True)
34+
class _TestCase:
35+
title: str | None = None
36+
# We can't use just `Inputs` because `mypy` complains about type incompatibility,
37+
# which is not true.
38+
# pylint: disable=unnecessary-lambda
39+
inputs: Inputs = dataclasses.field(default_factory=lambda: Inputs())
40+
options: Options = dataclasses.field(default_factory=lambda: Options())
41+
expected_options: Options = dataclasses.field(default_factory=lambda: Options())
42+
# pylint: enable=unnecessary-lambda
43+
expected_warnings: list[str] = dataclasses.field(default_factory=list)
44+
45+
46+
_cases = [
47+
_TestCase(title="No options"),
48+
_TestCase(title="Empty", inputs=Inputs(show_lines="")),
49+
_TestCase(
50+
inputs=Inputs(
51+
show_lines=",".join(
52+
[
53+
"1:2",
54+
"4+5",
55+
"6",
56+
"7:10",
57+
"-1",
58+
"-1:-2",
59+
"0",
60+
"0:",
61+
":0",
62+
"-1:0:2",
63+
"3:1",
64+
"a",
65+
" 18",
66+
" 19:20",
67+
"",
68+
" ",
69+
]
70+
),
71+
),
72+
expected_options=Options(
73+
show_lines=LinesRanges(
74+
{
75+
LinesRange(start=1, end=2),
76+
LinesRange(start=6, end=6),
77+
LinesRange(start=7, end=10),
78+
LinesRange(start=18, end=18),
79+
LinesRange(start=19, end=20),
80+
},
81+
)
82+
),
83+
expected_warnings=[
84+
"Range 2 ('4+5') is invalid: invalid literal for int() with base 10: '4+5'",
85+
"Range 5 ('-1') is invalid: Start must be at least 1",
86+
"Range 6 ('-1:-2') is invalid: Start must be at least 1",
87+
"Range 7 ('0') is invalid: Start must be at least 1",
88+
"Range 8 ('0:') is invalid: Start must be at least 1",
89+
"Range 9 (':0') is invalid: End must be at least 1",
90+
"Range 10 ('-1:0:2') is invalid: invalid literal for int() with base 10: '0:2'",
91+
"Range 11 ('3:1') is invalid: Start must be less than or equal to end",
92+
"Range 12 ('a') is invalid: invalid literal for int() with base 10: 'a'",
93+
"Range 15 ('') is invalid: Empty start",
94+
"Range 16 (' ') is invalid: Empty start",
95+
],
96+
),
97+
]
98+
99+
100+
@pytest.mark.parametrize(
101+
"case",
102+
_cases,
103+
ids=lambda c: c.title if c.title else repr(c.inputs.get("show_lines")),
104+
)
105+
def test_do_validate(
106+
case: _TestCase,
107+
highlight_validator_mock: mock.MagicMock, # pylint: disable=redefined-outer-name
108+
caplog: pytest.LogCaptureFixture,
109+
) -> None:
110+
"""Test valid initializations succeed."""
111+
language = "xxx"
112+
attrs = {"blah": "bleh"}
113+
md = mock.MagicMock(spec=markdown.Markdown)
114+
show_lines_input = case.inputs.get("show_lines")
115+
assert do_validate(language, case.inputs, case.options, attrs, md)
116+
highlight_validator_mock.assert_called_once_with(
117+
language, case.inputs, case.expected_options, attrs, md
118+
)
119+
if case.expected_warnings:
120+
assert show_lines_input is not None
121+
prefix = (
122+
f"Invalid `show_lines` option in {show_lines_input!r}, some lines will "
123+
"not be filtered: "
124+
)
125+
assert [prefix + warn for warn in case.expected_warnings] == [
126+
r.message for r in caplog.records
127+
]

0 commit comments

Comments
 (0)