Skip to content

Commit 13ace64

Browse files
committed
Add tests
1 parent f52be10 commit 13ace64

File tree

10 files changed

+127
-29
lines changed

10 files changed

+127
-29
lines changed

.github/workflows/tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
5252
- name: Test with pytest
5353
run: |
54-
pytest --cov --cov-fail-under=100
54+
pytest --cov
5555
5656
- name: Report coverage
5757
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'

pyproject.toml

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ description = run tests
7171
deps =
7272
-r tests/requirements.txt
7373
commands =
74-
pytest {posargs}
74+
pytest {posargs:--cov}
7575
7676
[testenv:profile]
7777
description = run profiler (use e.g. `firefox .tox/prof/combined.svg` to open)
@@ -135,20 +135,7 @@ commands =
135135

136136
[tool.coverage.run]
137137
source = ["mdformat"]
138-
omit = ["*/__main__.py"]
139-
140-
[tool.coverage.report]
141-
# Regexes for lines to exclude from consideration
142-
exclude_lines = [
143-
# Re-enable the standard pragma (with extra strictness)
144-
'# pragma: no cover\b',
145-
# Ellipsis lines after @typing.overload
146-
'^ +\.\.\.$',
147-
# Code for static type checkers
148-
"if TYPE_CHECKING:",
149-
# Scripts
150-
'if __name__ == .__main__.:',
151-
]
138+
plugins = ["covdefaults"]
152139

153140

154141
[tool.mypy]

src/mdformat/_cli.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
class RendererWarningPrinter(logging.Handler):
2323
def emit(self, record: logging.LogRecord) -> None:
24-
if record.levelno >= logging.WARNING:
24+
if record.levelno >= logging.WARNING: # pragma: no branch
2525
sys.stderr.write(f"Warning: {record.msg}\n")
2626

2727

@@ -59,10 +59,10 @@ def run(cli_args: Sequence[str]) -> int: # noqa: C901
5959
return 1
6060
opts: Mapping = {**DEFAULT_OPTS, **toml_opts, **cli_opts}
6161

62-
if sys.version_info >= (3, 13): # pragma: no cover
62+
if sys.version_info >= (3, 13): # pragma: >=3.13 cover
6363
if is_excluded(path, opts["exclude"], toml_path, "exclude" in cli_opts):
6464
continue
65-
else: # pragma: no cover
65+
else: # pragma: <3.13 cover
6666
if "exclude" in toml_opts:
6767
print_error(
6868
"'exclude' patterns are only available on Python 3.13+.",
@@ -172,7 +172,7 @@ def make_arg_parser(
172172
choices=("lf", "crlf", "keep"),
173173
help="output file line ending mode (default: lf)",
174174
)
175-
if sys.version_info >= (3, 13): # pragma: no cover
175+
if sys.version_info >= (3, 13): # pragma: >=3.13 cover
176176
parser.add_argument(
177177
"--exclude",
178178
action="append",
@@ -218,7 +218,7 @@ def resolve_file_paths(path_strings: Iterable[str]) -> list[None | Path]:
218218
return file_paths
219219

220220

221-
def is_excluded(
221+
def is_excluded( # pragma: >=3.13 cover
222222
path: Path | None,
223223
patterns: list[str],
224224
toml_path: Path | None,

src/mdformat/_compat.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import sys
44

5-
if sys.version_info >= (3, 11): # pragma: no cover
5+
if sys.version_info >= (3, 11): # pragma: >=3.11 cover
66
import tomllib
7-
else: # pragma: no cover
7+
else: # pragma: <3.11 cover
88
import tomli as tomllib
99

10-
if sys.version_info >= (3, 10): # pragma: no cover
10+
if sys.version_info >= (3, 10): # pragma: >=3.10 cover
1111
from importlib import metadata as importlib_metadata
12-
else: # pragma: no cover
12+
else: # pragma: <3.10 cover
1313
import importlib_metadata

src/mdformat/_conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def _validate_values(opts: Mapping, conf_path: Path) -> None: # noqa: C901
5959
if "number" in opts:
6060
if not isinstance(opts["number"], bool):
6161
raise InvalidConfError(f"Invalid 'number' value in {conf_path}")
62-
if "exclude" in opts:
62+
if "exclude" in opts: # pragma: >=3.13 cover
6363
if not isinstance(opts["exclude"], list):
6464
raise InvalidConfError(f"Invalid 'exclude' value in {conf_path}")
6565
for pattern in opts["exclude"]:

tests/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pytest
22
pytest-randomly
33
pytest-cov
4+
covdefaults

tests/test_api.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import os
22

3+
from markdown_it import MarkdownIt
34
import pytest
45

56
import mdformat
67
from mdformat._util import is_md_equal
8+
from mdformat.renderer import MDRenderer
79

810
UNFORMATTED_MARKDOWN = "\n\n# A header\n\n"
911
FORMATTED_MARKDOWN = "# A header\n"
@@ -127,3 +129,15 @@ def test_no_timestamp_modify(tmp_path):
127129
# Assert that modification time does not change when no changes are applied
128130
mdformat.file(file_path)
129131
assert os.path.getmtime(file_path) == initial_mod_time
132+
133+
134+
def test_mdrenderer_no_finalize(tmp_path):
135+
mdit = MarkdownIt()
136+
mdit.options["store_labels"] = True
137+
env = {}
138+
tokens = mdit.parse(
139+
"[gl ref]: https://gitlab.com\n\nHere's a link to [GitLab][gl ref]", env
140+
)
141+
unfinalized = MDRenderer().render(tokens, {}, env, finalize=False)
142+
finalized = MDRenderer().render(tokens, {}, env)
143+
assert finalized == unfinalized + "\n\n[gl ref]: https://gitlab.com\n"

tests/test_cli.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ def test_format__folder(tmp_path):
3737
assert file_path_3.read_text() == UNFORMATTED_MARKDOWN
3838

3939

40+
def test_format__folder_leads_to_invalid(tmp_path):
41+
file_path_1 = tmp_path / "test_markdown1.md"
42+
file_path_1.mkdir()
43+
assert run((str(tmp_path),)) == 0
44+
assert file_path_1.is_dir()
45+
46+
4047
def test_format__symlinks(tmp_path):
4148
# Create two MD files
4249
file_path_1 = tmp_path / "test_markdown1.md"
@@ -62,6 +69,19 @@ def test_format__symlinks(tmp_path):
6269
assert symlink_2.is_symlink()
6370

6471

72+
def test_broken_symlink(tmp_path, capsys):
73+
# Create a broken symlink
74+
file_path = tmp_path / "test_markdown1.md"
75+
symlink_path = tmp_path / "symlink"
76+
symlink_path.symlink_to(file_path)
77+
78+
with pytest.raises(SystemExit) as exc_info:
79+
run([str(symlink_path)])
80+
assert exc_info.value.code == 2
81+
captured = capsys.readouterr()
82+
assert "does not exist" in captured.err
83+
84+
6585
def test_invalid_file(capsys):
6686
with pytest.raises(SystemExit) as exc_info:
6787
run(("this is not a valid filepath?`=|><@{[]\\/,.%¤#'",))
@@ -70,6 +90,15 @@ def test_invalid_file(capsys):
7090
assert "does not exist" in captured.err
7191

7292

93+
def test_fifo(tmp_path, capsys):
94+
fifo_path = tmp_path / "fifo1"
95+
os.mkfifo(fifo_path)
96+
with pytest.raises(SystemExit) as exc_info:
97+
run((str(fifo_path),))
98+
assert exc_info.value.code == 2
99+
assert "does not exist" in capsys.readouterr().err
100+
101+
73102
def test_check(tmp_path):
74103
file_path = tmp_path / "test_markdown.md"
75104
file_path.write_bytes(FORMATTED_MARKDOWN.encode())

tests/test_config_file.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
from mdformat._cli import run
88
from mdformat._conf import read_toml_opts
9+
from tests.test_cli import FORMATTED_MARKDOWN, UNFORMATTED_MARKDOWN
910

1011

1112
def test_cli_override(tmp_path):
1213
config_path = tmp_path / ".mdformat.toml"
13-
config_path.write_text("wrap = 'no'")
14+
config_path.write_text("wrap = 'no'\nend_of_line = 'lf'")
1415

1516
file_path = tmp_path / "test_markdown.md"
1617
file_path.write_text("remove\nthis\nwrap\n")
@@ -65,9 +66,13 @@ def test_invalid_toml(tmp_path, capsys):
6566
("wrap", "wrap = -3"),
6667
("end_of_line", "end_of_line = 'lol'"),
6768
("number", "number = 0"),
69+
("exclude", "exclude = '**'"),
70+
("exclude", "exclude = ['1',3]"),
6871
],
6972
)
7073
def test_invalid_conf_value(bad_conf, conf_key, tmp_path, capsys):
74+
if conf_key == "exclude" and sys.version_info < (3, 13):
75+
pytest.skip("exclude conf only on Python 3.13+")
7176
config_path = tmp_path / ".mdformat.toml"
7277
config_path.write_text(bad_conf)
7378

@@ -91,3 +96,53 @@ def test_conf_with_stdin(tmp_path, capfd, monkeypatch):
9196
assert run(("-",)) == 0
9297
captured = capfd.readouterr()
9398
assert captured.out == "1. one\n2. two\n3. three\n"
99+
100+
101+
@pytest.mark.skipif(
102+
sys.version_info >= (3, 13), reason="'exclude' only possible on 3.13+"
103+
)
104+
def test_exclude_conf_on_old_python(tmp_path, capsys):
105+
config_path = tmp_path / ".mdformat.toml"
106+
config_path.write_text("exclude = ['**']")
107+
108+
file_path = tmp_path / "test_markdown.md"
109+
file_path.write_text("# Test Markdown")
110+
111+
assert run((str(file_path),)) == 1
112+
assert "only available on Python 3.13+" in capsys.readouterr().err
113+
114+
115+
@pytest.mark.skipif(
116+
sys.version_info < (3, 13), reason="'exclude' only possible on 3.13+"
117+
)
118+
def test_exclude(tmp_path, capsys):
119+
config_path = tmp_path / ".mdformat.toml"
120+
config_path.write_text("exclude = ['dir1/*', 'file1.md']")
121+
122+
dir1_path = tmp_path / "dir1"
123+
file1_path = tmp_path / "file1.md"
124+
file2_path = tmp_path / "file2.md"
125+
file3_path = tmp_path / dir1_path / "file3.md"
126+
dir1_path.mkdir()
127+
file1_path.write_text(UNFORMATTED_MARKDOWN)
128+
file2_path.write_text(UNFORMATTED_MARKDOWN)
129+
file3_path.write_text(UNFORMATTED_MARKDOWN)
130+
131+
assert run((str(tmp_path),)) == 0
132+
assert file1_path.read_text() == UNFORMATTED_MARKDOWN
133+
assert file2_path.read_text() == FORMATTED_MARKDOWN
134+
assert file3_path.read_text() == UNFORMATTED_MARKDOWN
135+
136+
137+
@pytest.mark.skipif(
138+
sys.version_info < (3, 13), reason="'exclude' only possible on 3.13+"
139+
)
140+
def test_empty_exclude(tmp_path, capsys):
141+
config_path = tmp_path / ".mdformat.toml"
142+
config_path.write_text("exclude = []")
143+
144+
file1_path = tmp_path / "file1.md"
145+
file1_path.write_text(UNFORMATTED_MARKDOWN)
146+
147+
assert run((str(tmp_path),)) == 0
148+
assert file1_path.read_text() == FORMATTED_MARKDOWN

tests/test_plugins.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def test_table(monkeypatch):
103103
other text
104104
"""
105105
),
106-
extensions=["table"],
106+
extensions=["table", "table"],
107107
)
108108
assert text == dedent(
109109
"""\
@@ -195,7 +195,7 @@ def format_json(unformatted: str, _info_str: str) -> str:
195195
return json.dumps(parsed, indent=2) + "\n"
196196

197197

198-
def test_code_format_warnings(monkeypatch, tmp_path, capsys):
198+
def test_code_format_warnings__cli(monkeypatch, tmp_path, capsys):
199199
monkeypatch.setitem(CODEFORMATTERS, "json", JSONFormatterPlugin.format_json)
200200
file_path = tmp_path / "test_markdown.md"
201201
file_path.write_text("```json\nthis is invalid json\n```\n")
@@ -207,6 +207,18 @@ def test_code_format_warnings(monkeypatch, tmp_path, capsys):
207207
)
208208

209209

210+
def test_code_format_warnings__api(monkeypatch, caplog):
211+
monkeypatch.setitem(CODEFORMATTERS, "json", JSONFormatterPlugin.format_json)
212+
assert (
213+
mdformat.text("```json\nthis is invalid json\n```\n", codeformatters=("json",))
214+
== "```json\nthis is invalid json\n```\n"
215+
)
216+
assert (
217+
caplog.messages[0]
218+
== "Failed formatting content of a json code block (line 1 before formatting)"
219+
)
220+
221+
210222
def test_plugin_conflict(monkeypatch, tmp_path, capsys):
211223
"""Test a warning when plugins try to render same syntax."""
212224
plugin_name_1 = "plug1"

0 commit comments

Comments
 (0)