diff --git a/b2/_internal/arg_parser.py b/b2/_internal/arg_parser.py index 52a27245a..61a559f53 100644 --- a/b2/_internal/arg_parser.py +++ b/b2/_internal/arg_parser.py @@ -146,7 +146,9 @@ def _get_short_description(self) -> str: return '' def error(self, message): - self.print_help() + # Help printed because of a command line syntax error is part of the error + # report, not of the command output, hence it goes to stderr. + self.print_help(sys.stderr) self.exit(2, f'\n{self.prog}: error: {message}\n') diff --git a/changelog.d/493.fixed.md b/changelog.d/493.fixed.md new file mode 100644 index 000000000..e667be3b5 --- /dev/null +++ b/changelog.d/493.fixed.md @@ -0,0 +1 @@ +Print help triggered by a command line syntax error on stderr instead of stdout, so that a mistyped command no longer pollutes redirected or piped output. diff --git a/test/unit/console_tool/test_help.py b/test/unit/console_tool/test_help.py index 0d72c1b8c..8c7ec51c7 100644 --- a/test/unit/console_tool/test_help.py +++ b/test/unit/console_tool/test_help.py @@ -41,3 +41,29 @@ def test_help(b2_cli, flag, included, excluded, capsys): found.add(e) assert found.issuperset(included), f'expected {included!r} in {out!r}' assert found.isdisjoint(excluded), f'expected {excluded!r} not in {out!r}' + + +@pytest.mark.parametrize( + 'argv', + [ + pytest.param(['--nonexistent-flag'], id='unrecognized-argument'), + pytest.param(['sync'], id='missing-required-arguments'), + pytest.param(['sync', '--nonexistent-flag'], id='unrecognized-subcommand-argument'), + ], +) +def test_help_on_command_line_error_goes_to_stderr(b2_cli, argv, capsys): + """Help printed because of a command line error belongs on stderr, not stdout.""" + b2_cli.run(argv, expected_status=2, expected_stdout=None) + + captured = capsys.readouterr() + assert captured.out == '' + assert 'error:' in captured.err + assert '-h, --help' in captured.err + + +def test_help_on_request_goes_to_stdout(b2_cli, capsys): + b2_cli.run(['--help'], expected_stdout=None) + + captured = capsys.readouterr() + assert '-h, --help' in captured.out + assert captured.err == ''