Coverage for tests/test_cli.py: 99.32%
104 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-02 00:26 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-02 00:26 +0200
1"""Tests for the `cli` module."""
3# IMPORTANT: Do not call `git_changelog.cli.main()`
4# without passing a config file path, otherwise
5# it will use its own config file and possibly modify
6# the CHANGELOG!
8from __future__ import annotations
10import os
11import sys
12from textwrap import dedent
13from typing import TYPE_CHECKING, Any, Iterator
15import pytest
16import tomli_w
18from git_changelog import cli, debug
20if TYPE_CHECKING:
21 from pathlib import Path
23 from tests.helpers import GitRepo
26if sys.version_info >= (3, 11):
27 from contextlib import chdir
28else:
29 # TODO: remove once support for Python 3.10 is dropped
30 from contextlib import contextmanager
32 @contextmanager
33 def chdir(path: str) -> Iterator[None]: # noqa: D103 33 ↛ 32line 33 didn't jump to line 32
34 old_wd = os.getcwd()
35 os.chdir(path)
36 try:
37 yield
38 finally:
39 os.chdir(old_wd)
42# IMPORTANT: See top module comment.
43def test_main(tmp_path: Path) -> None:
44 """Basic CLI test.
46 Parameters:
47 tmp_path: A temporary path to write an empty config to.
48 """
49 assert cli.main(["--config-file", str(tmp_path / "conf.toml")]) == 0
52# IMPORTANT: See top module comment.
53def test_show_help(capsys: pytest.CaptureFixture) -> None:
54 """Show help.
56 Parameters:
57 capsys: Pytest fixture to capture output.
58 """
59 with pytest.raises(SystemExit):
60 cli.main(["-h"])
61 captured = capsys.readouterr()
62 assert "git-changelog" in captured.out
65def test_get_version() -> None:
66 """Get self version."""
67 assert cli.get_version()
70@pytest.mark.parametrize(
71 "args",
72 [
73 (".", "-s", "feat,fix"),
74 ("-s", "feat,fix", "."),
75 ],
76)
77def test_passing_repository_and_sections(tmp_path: Path, args: tuple[str]) -> None:
78 """Render the changelog of given repository, choosing sections.
80 Parameters:
81 tmp_path: A temporary path to write the changelog into.
82 args: Command line arguments.
83 """
84 ch = tmp_path.joinpath("ch.md")
85 parsed_settings = cli.parse_settings([*args, "-o", ch.as_posix(), "-c", "angular"])
87 assert parsed_settings["output"] == str(ch.as_posix())
88 assert parsed_settings["sections"] == ["feat", "fix"]
89 assert parsed_settings["repository"] == "."
90 assert parsed_settings["convention"] == "angular"
93@pytest.mark.parametrize("is_pyproject", [True, False, None])
94@pytest.mark.parametrize(
95 ("sections", "sections_value"),
96 [
97 (None, None),
98 ("", None),
99 (",,", None),
100 ("a, b, ", ["a", "b"]),
101 ("a, , ", ["a"]),
102 ("a, b, c", ["a", "b", "c"]),
103 (["a", "b", "c"], ["a", "b", "c"]),
104 # Uncomment if None/null is once allowed as a value
105 # ("none", None),
106 # ("none, none, none", None),
107 ],
108)
109@pytest.mark.parametrize("parse_refs", [None, False, True])
110def test_config_reading(
111 tmp_path: Path,
112 is_pyproject: bool | None,
113 sections: str | list[str] | None,
114 sections_value: list | None,
115 parse_refs: bool | None,
116) -> None:
117 """Check settings files are correctly interpreted.
119 Parameters:
120 tmp_path: A temporary path to write the settings file into.
121 is_pyproject: Controls whether a `pyproject.toml` (`True`),
122 a `.git-changelog.toml` (`False`) or a custom file (`None`) is being tested.
123 sections: A `sections` config to override defaults.
124 sections_value: The expectation for `sections` after reading the config file.
125 parse_refs: An explicit override of the `parse_refs` of the config (if boolean)
126 or skip writing the override into the test config file (`None`).
127 """
128 with chdir(str(tmp_path)):
129 config_content: dict[str, Any] = {}
131 if sections is not None:
132 config_content["sections"] = sections
134 if parse_refs is not None:
135 config_content["parse_refs"] = parse_refs
137 config_fname = "custom-file.toml" if is_pyproject is None else ".git-changelog.toml"
138 config_fname = "pyproject.toml" if is_pyproject else config_fname
139 tmp_path.joinpath(config_fname).write_text(
140 tomli_w.dumps(
141 config_content if not is_pyproject else {"tool": {"git-changelog": config_content}},
142 ),
143 )
145 settings = cli.read_config(tmp_path / config_fname) if config_fname == "custom-file.toml" else cli.read_config()
147 ground_truth: dict[str, Any] = cli.DEFAULT_SETTINGS.copy()
148 ground_truth["sections"] = sections_value
149 ground_truth["parse_refs"] = bool(parse_refs)
151 assert settings == ground_truth
154@pytest.mark.parametrize("value", [None, False, True])
155def test_settings_warning(
156 tmp_path: Path,
157 value: bool,
158) -> None:
159 """Check warning on bump_latest.
161 Parameters:
162 tmp_path: A temporary path to write the settings file into.
163 """
164 with chdir(str(tmp_path)):
165 args: list[str] = []
166 if value is not None:
167 (tmp_path / ".git-changelog.toml").write_text(
168 tomli_w.dumps({"bump_latest": value}),
169 )
170 else:
171 args = ["--bump-latest"]
173 with pytest.warns(FutureWarning) as record:
174 cli.parse_settings(args)
176 solution = "is deprecated in favor of" # Warning comes from CLI parsing.
177 if value is not None: # Warning is issued when parsing the config file.
178 solution = "remove" if not value else "auto"
180 assert len(record) == 1
181 assert solution in str(record[0].message)
183 # If setting is in config file AND passed by CLI, two FutureWarnings are issued.
184 if (tmp_path / ".git-changelog.toml").exists():
185 with pytest.warns(FutureWarning) as record:
186 cli.parse_settings(["--bump-latest"])
188 assert len(record) == 2
191# IMPORTANT: See top module comment.
192def test_show_version(capsys: pytest.CaptureFixture) -> None:
193 """Show version.
195 Parameters:
196 capsys: Pytest fixture to capture output.
197 """
198 with pytest.raises(SystemExit):
199 cli.main(["-V"])
200 captured = capsys.readouterr()
201 assert debug.get_version() in captured.out
204# IMPORTANT: See top module comment.
205def test_show_debug_info(capsys: pytest.CaptureFixture) -> None:
206 """Show debug information.
208 Parameters:
209 capsys: Pytest fixture to capture output.
210 """
211 with pytest.raises(SystemExit):
212 cli.main(["--debug-info"])
213 captured = capsys.readouterr().out.lower()
214 assert "python" in captured
215 assert "system" in captured
216 assert "environment" in captured
217 assert "packages" in captured
220# IMPORTANT: See top module comment.
221def test_jinja_context(repo: GitRepo) -> None:
222 """Render template with custom template variables.
224 Parameters:
225 repo: Temporary Git repository (fixture).
226 """
227 repo.path.joinpath("conf.toml").write_text(
228 dedent(
229 """
230 [jinja_context]
231 k1 = "ignored"
232 k2 = "v2"
233 k3 = "v3"
234 """,
235 ),
236 )
238 template = repo.path.joinpath(".custom_template.md.jinja")
239 template.write_text("{% for key, val in jinja_context.items() %}{{ key }} = {{ val }}\n{% endfor %}")
241 exit_code = cli.main(
242 [
243 "--config-file",
244 str(repo.path / "conf.toml"),
245 "-o",
246 str(repo.path / "CHANGELOG.md"),
247 "-t",
248 f"path:{template}",
249 "--jinja-context",
250 "k1=v1",
251 "-j",
252 "k3=v3",
253 str(repo.path),
254 ],
255 )
257 assert exit_code == 0
259 contents = repo.path.joinpath("CHANGELOG.md").read_text()
260 assert contents == "k1 = v1\nk2 = v2\nk3 = v3\n"
263# IMPORTANT: See top module comment.
264def test_versioning(repo: GitRepo) -> None:
265 """Use a specific versioning scheme.
267 Parameters:
268 repo: Temporary Git repository (fixture).
269 """
270 repo.commit("feat: Feature")
271 repo.tag("1.0.0")
272 repo.commit("fix: Fix")
273 with repo.enter():
274 assert cli.main(["-cconventional", "-nsemver", "-Bauto"]) == 0
275 assert cli.main(["-cconventional", "-npep440", "-Bauto"]) == 0
276 assert cli.main(["-cconventional", "-nsemver", "-B1.1.0"]) == 0
277 assert cli.main(["-cconventional", "-npep440", "-B1.1.0"]) == 0
278 assert cli.main(["-cconventional", "-nsemver", "-Bunknown"]) == 1
279 assert cli.main(["-cconventional", "-npep440", "-Bunknown"]) == 1
280 assert cli.main(["-cconventional", "-npep440", "-Balpha"]) == 1