Skip to content

Commit e88f0a9

Browse files
committed
Fixed bad options not returning an error code
Fixes #153
1 parent 7d14065 commit e88f0a9

8 files changed

+49
-54
lines changed

bumpversion/cli.py

-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424

2525
@click.group(
2626
context_settings={
27-
"ignore_unknown_options": True,
28-
"allow_interspersed_args": True,
2927
"help_option_names": ["-h", "--help"],
3028
},
3129
add_help_option=False,

tests/conftest.py

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import subprocess
44
from contextlib import contextmanager
5+
from click.testing import CliRunner
56
from pathlib import Path
67
from typing import Generator
78

@@ -62,3 +63,10 @@ def hg_repo(tmp_path: Path) -> Path:
6263
"""Generate a simple temporary mercurial repo and return the path."""
6364
subprocess.run(["hg", "init"], cwd=tmp_path, check=True, capture_output=True)
6465
return tmp_path
66+
67+
68+
@pytest.fixture
69+
def runner() -> CliRunner:
70+
"""Return a CliRunner instance."""
71+
72+
return CliRunner()

tests/test_cli/test_base_cli.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Tests for the base CLI commands."""
2+
3+
from bumpversion import cli
4+
5+
6+
class TestBadOptionRaisesError:
7+
"""Tests to ensure that bad options raise an error."""
8+
9+
def test_bad_option_raises_error(self, runner):
10+
"""Passing an invalid option should raise an error."""
11+
result = runner.invoke(cli.cli, ["--bad-option", "bump", "--current-version", "1.0.0", "patch"])
12+
assert result.exit_code != 0
13+
assert "No such option: --bad-option" in result.output

tests/test_cli/test_bump.py

+10-20
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import pytest
1010
from pytest import param
1111

12-
from click.testing import CliRunner, Result
12+
from click.testing import Result
1313

1414
from bumpversion import cli
1515
from tests.conftest import inside_dir
@@ -18,12 +18,11 @@
1818
class TestNoConfiguredFilesOption:
1919
"""Tests around the behavior of the --no-configured-files option."""
2020

21-
def test_no_files_marked_to_modify(self, mocker, tmp_path: Path):
21+
def test_no_files_marked_to_modify(self, mocker, tmp_path: Path, runner):
2222
"""
2323
No files are sent to the `do_bump` function if the --no-configured-files option is used.
2424
"""
2525
mocked_do_bump = mocker.patch("bumpversion.cli.do_bump")
26-
runner: CliRunner = CliRunner()
2726
with inside_dir(tmp_path):
2827
result: Result = runner.invoke(
2928
cli.cli, ["bump", "--current-version", "1.0.0", "--no-configured-files", "patch"]
@@ -38,10 +37,9 @@ def test_no_files_marked_to_modify(self, mocker, tmp_path: Path):
3837
passed_config = call_args[2]
3938
assert len(passed_config.files) == 0
4039

41-
def test_file_args_marked_to_modify(self, mocker, tmp_path: Path):
40+
def test_file_args_marked_to_modify(self, mocker, tmp_path: Path, runner):
4241
"""File paths marked in the command-line arguments are sent to the `do_bump` function."""
4342
mocked_do_bump = mocker.patch("bumpversion.cli.do_bump")
44-
runner: CliRunner = CliRunner()
4543
with inside_dir(tmp_path):
4644
result: Result = runner.invoke(
4745
cli.cli,
@@ -59,7 +57,7 @@ def test_file_args_marked_to_modify(self, mocker, tmp_path: Path):
5957
assert passed_config.files[0].filename == "do-this-file.txt"
6058

6159

62-
def test_bump_nested_regex(tmp_path: Path, fixtures_path: Path, caplog):
60+
def test_bump_nested_regex(tmp_path: Path, fixtures_path: Path, caplog, runner):
6361
"""
6462
Arrange/Act: Run the `bump` subcommand with --no-configured-files.
6563
@@ -71,7 +69,6 @@ def test_bump_nested_regex(tmp_path: Path, fixtures_path: Path, caplog):
7169
config_path = tmp_path / ".bumpversion.toml"
7270
config_path.write_text(content)
7371

74-
runner: CliRunner = CliRunner()
7572
with inside_dir(tmp_path):
7673
result: Result = runner.invoke(cli.cli, ["bump", "-vv", "patch"])
7774

@@ -85,23 +82,21 @@ def test_bump_nested_regex(tmp_path: Path, fixtures_path: Path, caplog):
8582
assert cff_path.read_text() == f"cff-version: 1.2.0\ndate-released: {now}\n"
8683

8784

88-
def test_missing_explicit_config_file(tmp_path: Path):
85+
def test_missing_explicit_config_file(tmp_path: Path, runner):
8986
"""The command-line processor should raise an exception if the config file is missing."""
9087
with inside_dir(tmp_path):
91-
runner: CliRunner = CliRunner()
9288
with inside_dir(tmp_path):
9389
result: Result = runner.invoke(cli.cli, ["bump", "--config-file", "missing-file.cfg"])
9490
assert result.exit_code != 0
9591
assert "'missing-file.cfg' does not exist." in result.output
9692

9793

98-
def test_cli_options_override_config(tmp_path: Path, fixtures_path: Path, mocker):
94+
def test_cli_options_override_config(tmp_path: Path, fixtures_path: Path, mocker, runner):
9995
"""The command-line processor should override the config file."""
10096
# Arrange
10197
config_path = tmp_path / "this_config.toml"
10298
fixture_toml = fixtures_path / "basic_cfg.toml"
10399
shutil.copy(fixture_toml, config_path)
104-
runner: CliRunner = CliRunner()
105100
mocked_do_bump = mocker.patch("bumpversion.cli.do_bump")
106101

107102
# Act
@@ -163,13 +158,12 @@ def test_cli_options_override_config(tmp_path: Path, fixtures_path: Path, mocker
163158
param("hg_repo", "hg", id="hg"),
164159
],
165160
)
166-
def test_dirty_work_dir_raises_error(repo: str, scm_command: str, request):
161+
def test_dirty_work_dir_raises_error(repo: str, scm_command: str, request, runner):
167162
repo_path: Path = request.getfixturevalue(repo)
168163
with inside_dir(repo_path):
169164
# Arrange
170165
repo_path.joinpath("dirty2").write_text("i'm dirty! 1.1.1")
171166
subprocess.run([scm_command, "add", "dirty2"], check=True)
172-
runner: CliRunner = CliRunner()
173167

174168
# Act
175169
result: Result = runner.invoke(
@@ -181,7 +175,7 @@ def test_dirty_work_dir_raises_error(repo: str, scm_command: str, request):
181175
assert "working directory is not clean" in result.output
182176

183177

184-
def test_non_scm_operations_if_scm_not_installed(tmp_path: Path, monkeypatch):
178+
def test_non_scm_operations_if_scm_not_installed(tmp_path: Path, monkeypatch, runner):
185179
"""Everything works except SCM commands if the SCM is unusable."""
186180
# Arrange
187181
monkeypatch.setenv("PATH", "")
@@ -190,8 +184,6 @@ def test_non_scm_operations_if_scm_not_installed(tmp_path: Path, monkeypatch):
190184
version_path = tmp_path / "VERSION"
191185
version_path.write_text("31.0.3")
192186

193-
runner: CliRunner = CliRunner()
194-
195187
# Act
196188
runner.invoke(cli.cli, ["bump", "major", "--current-version", "31.0.3", "VERSION"])
197189

@@ -206,7 +198,7 @@ def test_non_scm_operations_if_scm_not_installed(tmp_path: Path, monkeypatch):
206198
param("", id="missing_version_part"),
207199
],
208200
)
209-
def test_detects_bad_or_missing_version_part(version_part: str, tmp_path: Path, monkeypatch):
201+
def test_detects_bad_or_missing_version_part(version_part: str, tmp_path: Path, monkeypatch, runner):
210202
"""It properly detects bad or missing version part."""
211203
# Arrange
212204
monkeypatch.setenv("PATH", "")
@@ -215,7 +207,6 @@ def test_detects_bad_or_missing_version_part(version_part: str, tmp_path: Path,
215207
version_path = tmp_path / "VERSION"
216208
version_path.write_text("31.0.3")
217209

218-
runner: CliRunner = CliRunner()
219210
args = ["bump", "--current-version", "31.0.3"]
220211
if version_part:
221212
args.append(version_part)
@@ -228,7 +219,7 @@ def test_detects_bad_or_missing_version_part(version_part: str, tmp_path: Path,
228219
assert "Unknown version part:" in result.stdout
229220

230221

231-
def test_ignores_missing_files_with_option(tmp_path, fixtures_path):
222+
def test_ignores_missing_files_with_option(tmp_path, fixtures_path, runner):
232223
"""The replace subcommand should ignore missing."""
233224

234225
config_file = tmp_path / ".bumpversion.toml"
@@ -242,7 +233,6 @@ def test_ignores_missing_files_with_option(tmp_path, fixtures_path):
242233
)
243234

244235
# Act
245-
runner: CliRunner = CliRunner()
246236
with inside_dir(tmp_path):
247237
result: Result = runner.invoke(
248238
cli.cli,

tests/test_cli/test_replace.py

+9-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import traceback
55
from pathlib import Path
66

7-
from click.testing import CliRunner, Result
7+
from click.testing import Result
88

99
from bumpversion import cli
1010
from tests.conftest import inside_dir
@@ -41,15 +41,14 @@ class TestReplaceCLI:
4141
class TestDefaultReplacesVersion:
4242
"""Test the default behavior of the replace subcommand."""
4343

44-
def test_default_affects_all_configured_files(self, mocker, tmp_path, fixtures_path):
44+
def test_default_affects_all_configured_files(self, mocker, tmp_path, fixtures_path, runner):
4545
"""The replace subcommand should replace the version in all configured files."""
4646
# Arrange
4747
toml_path = fixtures_path / "basic_cfg.toml"
4848
config_path = tmp_path / "pyproject.toml"
4949
shutil.copy(toml_path, config_path)
5050

5151
mocked_modify_files = mocker.patch("bumpversion.cli.modify_files")
52-
runner: CliRunner = CliRunner()
5352
with inside_dir(tmp_path):
5453
result: Result = runner.invoke(cli.cli, ["replace", "--new-version", "1.1.0"])
5554

@@ -66,15 +65,14 @@ def test_default_affects_all_configured_files(self, mocker, tmp_path, fixtures_p
6665
actual_filenames = {f.file_change.filename for f in configured_files}
6766
assert actual_filenames == {"setup.py", "CHANGELOG.md", "bumpversion/__init__.py"}
6867

69-
def test_can_limit_to_specific_files(self, mocker, git_repo, fixtures_path):
68+
def test_can_limit_to_specific_files(self, mocker, git_repo, fixtures_path, runner):
7069
"""The replace subcommand should set the files to only the specified files."""
7170
# Arrange
7271
toml_path = fixtures_path / "basic_cfg.toml"
7372
config_path = git_repo / "pyproject.toml"
7473
shutil.copy(toml_path, config_path)
7574

7675
mocked_modify_files = mocker.patch("bumpversion.cli.modify_files")
77-
runner: CliRunner = CliRunner()
7876
with inside_dir(git_repo):
7977
result: Result = runner.invoke(cli.cli, ["replace", "--no-configured-files", "VERSION"])
8078

@@ -93,15 +91,14 @@ def test_can_limit_to_specific_files(self, mocker, git_repo, fixtures_path):
9391
class TestOptions:
9492
"""Test the options of the replace subcommand."""
9593

96-
def test_missing_newversion_is_set_to_none(self, mocker, tmp_path, fixtures_path):
94+
def test_missing_newversion_is_set_to_none(self, mocker, tmp_path, fixtures_path, runner):
9795
"""The replace subcommand should set new_version to None in the context."""
9896
# Arrange
9997
toml_path = fixtures_path / "basic_cfg.toml"
10098
config_path = tmp_path / "pyproject.toml"
10199
shutil.copy(toml_path, config_path)
102100

103101
mocked_modify_files = mocker.patch("bumpversion.cli.modify_files")
104-
runner: CliRunner = CliRunner()
105102
with inside_dir(tmp_path):
106103
result: Result = runner.invoke(cli.cli, ["replace"])
107104

@@ -115,7 +112,7 @@ def test_missing_newversion_is_set_to_none(self, mocker, tmp_path, fixtures_path
115112
call_args = mocked_modify_files.call_args[0]
116113
assert call_args[2] is None
117114

118-
def test_ignores_missing_files_with_option(self, mocker, tmp_path, fixtures_path):
115+
def test_ignores_missing_files_with_option(self, mocker, tmp_path, fixtures_path, runner):
119116
"""The replace subcommand should ignore missing."""
120117

121118
config_file = tmp_path / ".bumpversion.toml"
@@ -129,7 +126,6 @@ def test_ignores_missing_files_with_option(self, mocker, tmp_path, fixtures_path
129126
)
130127

131128
# Act
132-
runner: CliRunner = CliRunner()
133129
with inside_dir(tmp_path):
134130
result: Result = runner.invoke(
135131
cli.cli,
@@ -154,7 +150,7 @@ def test_ignores_missing_files_with_option(self, mocker, tmp_path, fixtures_path
154150
class TestSearchInputs:
155151
"""Test the search inputs of the replace subcommand."""
156152

157-
def test_accepts_plain_string(self, tmp_path, fixtures_path):
153+
def test_accepts_plain_string(self, tmp_path, fixtures_path, runner):
158154
"""Replace should not worry if the search values have version info."""
159155
from tomlkit import dumps
160156

@@ -164,7 +160,6 @@ def test_accepts_plain_string(self, tmp_path, fixtures_path):
164160
doc_path = tmp_path / "docs.yaml"
165161
doc_path.write_text("url: https://github.com/sampleuser/workflows/main/.github/update_mailmap.py")
166162

167-
runner: CliRunner = CliRunner()
168163
with inside_dir(tmp_path):
169164
result: Result = runner.invoke(
170165
cli.cli,
@@ -186,7 +181,7 @@ def test_accepts_plain_string(self, tmp_path, fixtures_path):
186181

187182
assert result.exit_code == 0
188183

189-
def test_unintentional_valid_regex_still_found(self, tmp_path: Path, caplog) -> None:
184+
def test_unintentional_valid_regex_still_found(self, tmp_path: Path, caplog, runner) -> None:
190185
"""A search string not meant to be a regex (but is) is still found and replaced correctly."""
191186
# Arrange
192187
search = "(unreleased)"
@@ -209,7 +204,6 @@ def test_unintentional_valid_regex_still_found(self, tmp_path: Path, caplog) ->
209204
)
210205

211206
# Act
212-
runner: CliRunner = CliRunner()
213207
with inside_dir(tmp_path):
214208
result: Result = runner.invoke(
215209
cli.cli,
@@ -241,7 +235,7 @@ def test_unintentional_valid_regex_still_found(self, tmp_path: Path, caplog) ->
241235
class TestReplaceInputs:
242236
"""Test the replace inputs of the replace subcommand."""
243237

244-
def test_accepts_empty_string(self, tmp_path, fixtures_path):
238+
def test_accepts_empty_string(self, tmp_path, fixtures_path, runner):
245239
"""Replace should be able to replace strings with an empty string."""
246240
from tomlkit import dumps
247241

@@ -251,7 +245,6 @@ def test_accepts_empty_string(self, tmp_path, fixtures_path):
251245
doc_path = tmp_path / "docs.yaml"
252246
doc_path.write_text("We should censor profanity\n\n")
253247

254-
runner: CliRunner = CliRunner()
255248
with inside_dir(tmp_path):
256249
result: Result = runner.invoke(
257250
cli.cli,
@@ -275,7 +268,7 @@ def test_accepts_empty_string(self, tmp_path, fixtures_path):
275268
assert result.exit_code == 0
276269
assert doc_path.read_text() == "We should censor \n\n"
277270

278-
def test_accepts_plain_string(self, tmp_path, fixtures_path):
271+
def test_accepts_plain_string(self, tmp_path, fixtures_path, runner):
279272
"""Replace should not worry if the replace values have version info."""
280273
from tomlkit import dumps
281274

@@ -285,7 +278,6 @@ def test_accepts_plain_string(self, tmp_path, fixtures_path):
285278
doc_path = tmp_path / "docs.yaml"
286279
doc_path.write_text("url: https://github.com/sampleuser/workflows/v2.17.7/.github/update_mailmap.py")
287280

288-
runner: CliRunner = CliRunner()
289281
with inside_dir(tmp_path):
290282
result: Result = runner.invoke(
291283
cli.cli,

tests/test_cli/test_show.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import shutil
22
from pathlib import Path
33

4-
from click.testing import CliRunner, Result
4+
from click.testing import Result
55

66
from bumpversion import cli
77
from tests.conftest import inside_dir
88

99

10-
def test_show(tmp_path: Path, fixtures_path: Path):
10+
def test_show(tmp_path: Path, fixtures_path: Path, runner):
1111
"""The show subcommand should list the parts of the configuration."""
1212
# Arrange
1313
config_path = tmp_path / "pyproject.toml"
1414
toml_path = fixtures_path / "basic_cfg.toml"
1515
shutil.copy(toml_path, config_path)
16-
runner: CliRunner = CliRunner()
1716

1817
with inside_dir(tmp_path):
1918
result: Result = runner.invoke(cli.cli, ["show", "current_version"])
@@ -26,13 +25,12 @@ def test_show(tmp_path: Path, fixtures_path: Path):
2625
assert result.output.splitlines(keepends=False) == ["1.0.0"]
2726

2827

29-
def test_show_and_increment(tmp_path: Path, fixtures_path: Path):
28+
def test_show_and_increment(tmp_path: Path, fixtures_path: Path, runner):
3029
"""The show subcommand should incrment the version and display it."""
3130
# Arrange
3231
config_path = tmp_path / "pyproject.toml"
3332
toml_path = fixtures_path / "basic_cfg.toml"
3433
shutil.copy(toml_path, config_path)
35-
runner: CliRunner = CliRunner()
3634

3735
with inside_dir(tmp_path):
3836
result: Result = runner.invoke(cli.cli, ["show", "new_version", "--increment", "minor"])
@@ -45,15 +43,14 @@ def test_show_and_increment(tmp_path: Path, fixtures_path: Path):
4543
assert result.output.splitlines(keepends=False) == ["1.1.0-dev"]
4644

4745

48-
def test_show_no_args(tmp_path: Path, fixtures_path: Path):
46+
def test_show_no_args(tmp_path: Path, fixtures_path: Path, runner):
4947
"""The show subcommand should list the entire configuration."""
5048
# Arrange
5149
config_path = tmp_path / "pyproject.toml"
5250
toml_path = fixtures_path / "basic_cfg.toml"
5351
expected_output = fixtures_path.joinpath("basic_cfg_expected.yaml").read_text()
5452

5553
shutil.copy(toml_path, config_path)
56-
runner: CliRunner = CliRunner()
5754

5855
with inside_dir(tmp_path):
5956
result: Result = runner.invoke(cli.cli, ["show", "--format", "yaml"])

0 commit comments

Comments
 (0)