import os import platform import sys from io import StringIO from unittest.mock import patch import arrow from click.testing import CliRunner from gitlint import __version__, cli from gitlint.shell import CommandNotFound from gitlint.tests.base import BaseTestCase from gitlint.utils import FILE_ENCODING, TERMINAL_ENCODING class CLITests(BaseTestCase): USAGE_ERROR_CODE = 253 GIT_CONTEXT_ERROR_CODE = 254 CONFIG_ERROR_CODE = 255 GITLINT_SUCCESS_CODE = 0 def setUp(self): super().setUp() self.cli = CliRunner() # Patch gitlint.cli.git_version() so that we don't have to patch it separately in every test self.git_version_path = patch("gitlint.cli.git_version") cli.git_version = self.git_version_path.start() cli.git_version.return_value = "git version 1.2.3" def tearDown(self): self.git_version_path.stop() @staticmethod def get_system_info_dict(): """Returns a dict with items related to system values logged by `gitlint --debug`""" return { "platform": platform.platform(), "python_version": sys.version, "gitlint_version": __version__, "GITLINT_USE_SH_LIB": BaseTestCase.GITLINT_USE_SH_LIB, "target": os.path.realpath(os.getcwd()), "TERMINAL_ENCODING": TERMINAL_ENCODING, "FILE_ENCODING": FILE_ENCODING, } def test_version(self): """Test for --version option""" result = self.cli.invoke(cli.cli, ["--version"]) self.assertEqual(result.output.split("\n")[0], f"cli, version {__version__}") @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_lint(self, sh, _): """Test for basic simple linting functionality""" sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360", "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\ncommït-title\n\ncommït-body", "#", # git config --get core.commentchar "1\t4\tfile1.txt\n3\t5\tpåth/to/file2.txt\n", "commit-1-branch-1\ncommit-1-branch-2\n", ] with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli) self.assertEqual(stderr.getvalue(), '3: B5 Body message is too short (11<20): "commït-body"\n') self.assertEqual(result.exit_code, 1) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_lint_multiple_commits(self, sh, _): """Test for --commits option""" # fmt: off sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360\n" + # git rev-list "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" "commït-title1\n\ncommït-body1", "#", # git config --get core.commentchar "3\t5\tcommit-1/file-1\n1\t4\tcommit-1/file-2\n", # git diff-tree "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains # git log --pretty "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" "commït-title2\n\ncommït-body2", "8\t3\tcommit-2/file-1\n1\t5\tcommit-2/file-2\n", # git diff-tree "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains # git log --pretty "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" "commït-title3\n\ncommït-body3", "7\t2\tcommit-3/file-1\n1\t7\tcommit-3/file-2\n", # git diff-tree "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains ] # fmt: on with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--commits", "foo...bar"]) self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_1")) self.assertEqual(result.exit_code, 3) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_lint_multiple_commits_csv(self, sh, _): """Test for --commits option""" # fmt: off sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360\n", # git rev-list "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n", "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" "commït-title1\n\ncommït-body1", "#", # git config --get core.commentchar "3\t5\tcommit-1/file-1\n1\t4\tcommit-1/file-2\n", # git diff-tree "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains # git log --pretty "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" "commït-title2\n\ncommït-body2", "8\t3\tcommit-2/file-1\n1\t5\tcommit-2/file-2\n", # git diff-tree "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains # git log --pretty "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" "commït-title3\n\ncommït-body3", "7\t2\tcommit-3/file-1\n1\t7\tcommit-3/file-2\n", # git diff-tree "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains ] # fmt: on with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--commits", "6f29bf81,25053cce,4da2656b"]) self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_csv_1")) self.assertEqual(result.exit_code, 3) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_lint_multiple_commits_config(self, sh, _): """Test for --commits option where some of the commits have gitlint config in the commit message""" # fmt: off # Note that the second commit title has a trailing period that is being ignored by gitlint-ignore: T3 sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360\n" + # git rev-list "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" "commït-title1\n\ncommït-body1", "#", # git config --get core.commentchar "9\t4\tcommit-1/file-1\n0\t2\tcommit-1/file-2\n", # git diff-tree "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains # git log --pretty "test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" "commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n", "3\t7\tcommit-2/file-1\n4\t6\tcommit-2/file-2\n", # git diff-tree "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains # git log --pretty "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" "commït-title3.\n\ncommït-body3", "3\t8\tcommit-3/file-1\n1\t4\tcommit-3/file-2\n", # git diff-tree "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains ] # fmt: on with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--commits", "foo...bar"]) # We expect that the second commit has no failures because of 'gitlint-ignore: T3' in its commit msg body self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_config_1")) self.assertEqual(result.exit_code, 3) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_lint_multiple_commits_configuration_rules(self, sh, _): """Test for --commits option where where we have configured gitlint to ignore certain rules for certain commits""" # fmt: off # Note that the second commit sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360\n" + # git rev-list "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" "commït-title1\n\ncommït-body1", "#", # git config --get core.commentchar "5\t9\tcommit-1/file-1\n1\t4\tcommit-1/file-2\n", # git diff-tree "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains # git log --pretty "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" # Normally T3 violation (trailing punctuation), but this commit is ignored because of # config below "commït-title2.\n\ncommït-body2\n", "4\t7\tcommit-2/file-1\n1\t4\tcommit-2/file-2\n", # git diff-tree "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains # git log --pretty "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" # Normally T1 and B5 violations, now only T1 because we're ignoring B5 in config below "commït-title3.\n\ncommït-body3 foo", "1\t9\tcommit-3/file-1\n3\t7\tcommit-3/file-2\n", # git diff-tree "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains ] # fmt: on with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke( cli.cli, [ "--commits", "foo...bar", "-c", "I1.regex=^commït-title2(.*)", "-c", "I2.regex=^commït-body3(.*)", "-c", "I2.ignore=B5", ], ) # We expect that the second commit has no failures because of it matching against I1.regex # Because we do test for the 3th commit to return violations, this test also ensures that a unique # config object is passed to each commit lint call expected = ( "Commit 6f29bf81a8:\n" '3: B5 Body message is too short (12<20): "commït-body1"\n\n' "Commit 4da2656b0d:\n" '1: T3 Title has trailing punctuation (.): "commït-title3."\n' ) self.assertEqual(stderr.getvalue(), expected) self.assertEqual(result.exit_code, 2) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_lint_commit(self, sh, _): """Test for --commit option""" # fmt: off sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360\n", # git log -1 --pretty=%H # git log --pretty "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" "WIP: commït-title1\n\ncommït-body1", "#", # git config --get core.commentchar "4\t5\tcommit-1/file-1\n1\t4\tcommit-1/file-2\n", # git diff-tree "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains ] # fmt: on with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--commit", "foo"]) self.assertEqual(result.output, "") self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_commit_1")) self.assertEqual(result.exit_code, 2) @patch("gitlint.cli.get_stdin_data", return_value=False) def test_lint_commit_negative(self, _): """Negative test for --commit option""" # Try using --commit and --commits at the same time (not allowed) result = self.cli.invoke(cli.cli, ["--commit", "foo", "--commits", "foo...bar"]) expected_output = "Error: --commit and --commits are mutually exclusive, use one or the other.\n" self.assertEqual(result.output, expected_output) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) @patch("gitlint.cli.get_stdin_data", return_value="WIP: tïtle \n") def test_input_stream(self, _): """Test for linting when a message is passed via stdin""" with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli) self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_input_stream_1")) self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") @patch("gitlint.cli.get_stdin_data", return_value="WIP: tïtle \n") def test_input_stream_debug(self, _): """Test for linting when a message is passed via stdin, and debug is enabled. This tests specifically that git commit meta is not fetched when not passing --staged""" with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--debug"]) self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_input_stream_debug_1")) self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") expected_kwargs = self.get_system_info_dict() expected_logs = self.get_expected("cli/test_cli/test_input_stream_debug_2", expected_kwargs) self.assert_logged(expected_logs) @patch("gitlint.cli.get_stdin_data", return_value="Should be ignored\n") @patch("gitlint.git.sh") def test_lint_ignore_stdin(self, sh, stdin_data): """Test for ignoring stdin when --ignore-stdin flag is enabled""" sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360", "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\ncommït-title\n\ncommït-body", "#", # git config --get core.commentchar "3\t12\tfile1.txt\n8\t5\tpåth/to/file2.txt\n", # git diff-tree "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains ] with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--ignore-stdin"]) self.assertEqual(stderr.getvalue(), '3: B5 Body message is too short (11<20): "commït-body"\n') self.assertEqual(result.exit_code, 1) # Assert that we didn't even try to get the stdin data self.assertEqual(stdin_data.call_count, 0) @patch("gitlint.cli.get_stdin_data", return_value="WIP: tïtle \n") @patch("arrow.now", return_value=arrow.get("2020-02-19T12:18:46.675182+01:00")) @patch("gitlint.git.sh") def test_lint_staged_stdin(self, sh, _, __): """Test for ignoring stdin when --ignore-stdin flag is enabled""" sh.git.side_effect = [ "#", # git config --get core.commentchar "1\t5\tcommit-1/file-1\n8\t9\tcommit-1/file-2\n", # git diff-tree "föo user\n", # git config --get user.name "föo@bar.com\n", # git config --get user.email "my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch) ] with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--debug", "--staged"]) self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_staged_stdin_1")) self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") expected_kwargs = self.get_system_info_dict() changed_files_stats = ( f" {os.path.join('commit-1', 'file-1')}: 1 additions, 5 deletions\n" f" {os.path.join('commit-1', 'file-2')}: 8 additions, 9 deletions" ) expected_kwargs.update({"changed_files_stats": changed_files_stats}) expected_logs = self.get_expected("cli/test_cli/test_lint_staged_stdin_2", expected_kwargs) self.assert_logged(expected_logs) @patch("arrow.now", return_value=arrow.get("2020-02-19T12:18:46.675182+01:00")) @patch("gitlint.git.sh") def test_lint_staged_msg_filename(self, sh, _): """Test for ignoring stdin when --ignore-stdin flag is enabled""" # fmt: off sh.git.side_effect = [ "#", # git config --get core.commentchar "3\t4\tcommit-1/file-1\n4\t7\tcommit-1/file-2\n", # git diff-tree "föo user\n", # git config --get user.name "föo@bar.com\n", # git config --get user.email "my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch) ] # fmt: on with self.tempdir() as tmpdir: msg_filename = os.path.join(tmpdir, "msg") with open(msg_filename, "w", encoding=FILE_ENCODING) as f: f.write("WIP: msg-filename tïtle\n") with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--debug", "--staged", "--msg-filename", msg_filename]) self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_staged_msg_filename_1")) self.assertEqual(result.exit_code, 2) self.assertEqual(result.output, "") expected_kwargs = self.get_system_info_dict() changed_files_stats = ( f" {os.path.join('commit-1', 'file-1')}: 3 additions, 4 deletions\n" f" {os.path.join('commit-1', 'file-2')}: 4 additions, 7 deletions" ) expected_kwargs.update({"changed_files_stats": changed_files_stats}) expected_logs = self.get_expected("cli/test_cli/test_lint_staged_msg_filename_2", expected_kwargs) self.assert_logged(expected_logs) @patch("gitlint.cli.get_stdin_data", return_value=False) def test_lint_staged_negative(self, _): result = self.cli.invoke(cli.cli, ["--staged"]) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) self.assertEqual( result.output, "Error: The 'staged' option (--staged) can only be used when using " "'--msg-filename' or when piping data to gitlint via stdin.\n", ) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_fail_without_commits(self, sh, _): """Test for --debug option""" sh.git.side_effect = ["", ""] # First invocation of git rev-list # Second invocation of git rev-list with patch("gitlint.display.stderr", new=StringIO()) as stderr: # By default, gitlint should silently exit with code GITLINT_SUCCESS when there are no commits result = self.cli.invoke(cli.cli, ["--commits", "foo..bar"]) self.assertEqual(stderr.getvalue(), "") self.assertEqual(result.exit_code, cli.GITLINT_SUCCESS) self.assert_log_contains('DEBUG: gitlint.cli No commits in range "foo..bar"') # When --fail-without-commits is set, gitlint should hard fail with code USAGE_ERROR_CODE self.clearlog() result = self.cli.invoke(cli.cli, ["--commits", "foo..bar", "--fail-without-commits"]) self.assertEqual(result.output, 'Error: No commits in range "foo..bar"\n') self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) self.assert_log_contains('DEBUG: gitlint.cli No commits in range "foo..bar"') @patch("gitlint.cli.get_stdin_data", return_value=False) def test_msg_filename(self, _): expected_output = "3: B6 Body message is missing\n" with self.tempdir() as tmpdir: msg_filename = os.path.join(tmpdir, "msg") with open(msg_filename, "w", encoding=FILE_ENCODING) as f: f.write("Commït title\n") with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename]) self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 1) self.assertEqual(result.output, "") @patch("gitlint.cli.get_stdin_data", return_value="WIP: tïtle \n") def test_silent_mode(self, _): """Test for --silent option""" with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--silent"]) self.assertEqual(stderr.getvalue(), "") self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") @patch("gitlint.cli.get_stdin_data", return_value="WIP: tïtle \n") def test_verbosity(self, _): """Test for --verbosity option""" # We only test -v and -vv, more testing is really not required here # -v with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["-v"]) self.assertEqual(stderr.getvalue(), "1: T2\n1: T5\n3: B6\n") self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") # -vv expected_output = ( "1: T2 Title has trailing whitespace\n" + "1: T5 Title contains the word 'WIP' (case-insensitive)\n" + "3: B6 Body message is missing\n" ) with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["-vv"], input="WIP: tïtle \n") self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") # -vvvv: not supported -> should print a config error with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["-vvvv"], input="WIP: tïtle \n") self.assertEqual(stderr.getvalue(), "") self.assertEqual(result.exit_code, CLITests.CONFIG_ERROR_CODE) self.assertEqual(result.output, "Config Error: Option 'verbosity' must be set between 0 and 3\n") @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_debug(self, sh, _): """Test for --debug option""" # fmt: off sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360\n" # git rev-list "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00a123\n" "commït-title1\n\ncommït-body1", "#", # git config --get core.commentchar "5\t8\tcommit-1/file-1\n2\t9\tcommit-1/file-2\n", # git diff-tree "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains "test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00b123\n" "commït-title2.\n\ncommït-body2", "5\t8\tcommit-2/file-1\n7\t9\tcommit-2/file-2\n", # git diff-tree "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00c123\n" "föobar\nbar", "1\t4\tcommit-3/file-1\n3\t4\tcommit-3/file-2\n", # git diff-tree "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains ] # fmt: on with patch("gitlint.display.stderr", new=StringIO()) as stderr: config_path = self.get_sample_path(os.path.join("config", "gitlintconfig")) result = self.cli.invoke(cli.cli, ["--config", config_path, "--debug", "--commits", "foo...bar"]) expected = ( "Commit 6f29bf81a8:\n3: B5\n\n" "Commit 25053ccec5:\n1: T3\n3: B5\n\n" "Commit 4da2656b0d:\n2: B4\n3: B5\n3: B6\n" ) self.assertEqual(stderr.getvalue(), expected) self.assertEqual(result.exit_code, 6) expected_kwargs = self.get_system_info_dict() changed_files_stats1 = ( f" {os.path.join('commit-1', 'file-1')}: 5 additions, 8 deletions\n" f" {os.path.join('commit-1', 'file-2')}: 2 additions, 9 deletions" ) changed_files_stats2 = ( f" {os.path.join('commit-2', 'file-1')}: 5 additions, 8 deletions\n" f" {os.path.join('commit-2', 'file-2')}: 7 additions, 9 deletions" ) changed_files_stats3 = ( f" {os.path.join('commit-3', 'file-1')}: 1 additions, 4 deletions\n" f" {os.path.join('commit-3', 'file-2')}: 3 additions, 4 deletions" ) expected_kwargs.update( { "changed_files_stats1": changed_files_stats1, "changed_files_stats2": changed_files_stats2, "changed_files_stats3": changed_files_stats3, } ) expected_kwargs.update({"config_path": config_path}) expected_logs = self.get_expected("cli/test_cli/test_debug_1", expected_kwargs) self.assert_logged(expected_logs) @patch("gitlint.cli.get_stdin_data", return_value="Test tïtle\n") def test_extra_path(self, _): """Test for --extra-path flag""" # Test extra-path pointing to a directory with patch("gitlint.display.stderr", new=StringIO()) as stderr: extra_path = self.get_sample_path("user_rules") result = self.cli.invoke(cli.cli, ["--extra-path", extra_path]) expected_output = '1: UC1 Commit violåtion 1: "Contënt 1"\n' + "3: B6 Body message is missing\n" self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 2) # Test extra-path pointing to a file with patch("gitlint.display.stderr", new=StringIO()) as stderr: extra_path = self.get_sample_path(os.path.join("user_rules", "my_commit_rules.py")) result = self.cli.invoke(cli.cli, ["--extra-path", extra_path]) expected_output = '1: UC1 Commit violåtion 1: "Contënt 1"\n' + "3: B6 Body message is missing\n" self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 2) @patch("gitlint.cli.get_stdin_data", return_value="Test tïtle\n") def test_extra_path_environment(self, _): """Test for GITLINT_EXTRA_PATH environment variable""" # Test setting extra-path to a directory from an environment variable with patch("gitlint.display.stderr", new=StringIO()) as stderr: extra_path = self.get_sample_path("user_rules") result = self.cli.invoke(cli.cli, env={"GITLINT_EXTRA_PATH": extra_path}) expected_output = '1: UC1 Commit violåtion 1: "Contënt 1"\n' + "3: B6 Body message is missing\n" self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 2) # Test extra-path pointing to a file from an environment variable with patch("gitlint.display.stderr", new=StringIO()) as stderr: extra_path = self.get_sample_path(os.path.join("user_rules", "my_commit_rules.py")) result = self.cli.invoke(cli.cli, env={"GITLINT_EXTRA_PATH": extra_path}) expected_output = '1: UC1 Commit violåtion 1: "Contënt 1"\n' + "3: B6 Body message is missing\n" self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 2) @patch("gitlint.cli.get_stdin_data", return_value="Test tïtle\n\nMy body that is long enough") def test_contrib(self, _): # Test enabled contrib rules with patch("gitlint.display.stderr", new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--contrib", "contrib-title-conventional-commits,CC1"]) expected_output = self.get_expected("cli/test_cli/test_contrib_1") self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 2) @patch("gitlint.cli.get_stdin_data", return_value="Test tïtle\n") def test_contrib_negative(self, _): result = self.cli.invoke(cli.cli, ["--contrib", "föobar,CC1"]) self.assertEqual(result.output, "Config Error: No contrib rule with id or name 'föobar' found.\n") self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) @patch("gitlint.cli.get_stdin_data", return_value="WIP: tëst") def test_config_file(self, _): """Test for --config option""" with patch("gitlint.display.stderr", new=StringIO()) as stderr: config_path = self.get_sample_path(os.path.join("config", "gitlintconfig")) result = self.cli.invoke(cli.cli, ["--config", config_path]) self.assertEqual(result.output, "") self.assertEqual(stderr.getvalue(), "1: T5\n3: B6\n") self.assertEqual(result.exit_code, 2) @patch("gitlint.cli.get_stdin_data", return_value="WIP: tëst") def test_config_file_environment(self, _): """Test for GITLINT_CONFIG environment variable""" with patch("gitlint.display.stderr", new=StringIO()) as stderr: config_path = self.get_sample_path(os.path.join("config", "gitlintconfig")) result = self.cli.invoke(cli.cli, env={"GITLINT_CONFIG": config_path}) self.assertEqual(result.output, "") self.assertEqual(stderr.getvalue(), "1: T5\n3: B6\n") self.assertEqual(result.exit_code, 2) def test_config_file_negative(self): """Negative test for --config option""" # Directory as config file config_path = self.get_sample_path("config") result = self.cli.invoke(cli.cli, ["--config", config_path]) expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' is a directory." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) # Non existing file config_path = self.get_sample_path("föo") result = self.cli.invoke(cli.cli, ["--config", config_path]) expected_string = f"Error: Invalid value for '-C' / '--config': File {config_path!r} does not exist." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) # Invalid config file config_path = self.get_sample_path(os.path.join("config", "invalid-option-value")) result = self.cli.invoke(cli.cli, ["--config", config_path]) self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) def test_config_file_negative_environment(self): """Negative test for GITLINT_CONFIG environment variable""" # Directory as config file config_path = self.get_sample_path("config") result = self.cli.invoke(cli.cli, env={"GITLINT_CONFIG": config_path}) expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' is a directory." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) # Non existing file config_path = self.get_sample_path("föo") result = self.cli.invoke(cli.cli, env={"GITLINT_CONFIG": config_path}) expected_string = f"Error: Invalid value for '-C' / '--config': File {config_path!r} does not exist." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) # Invalid config file config_path = self.get_sample_path(os.path.join("config", "invalid-option-value")) result = self.cli.invoke(cli.cli, env={"GITLINT_CONFIG": config_path}) self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) def test_config_error(self): result = self.cli.invoke(cli.cli, ["-c", "foo.bar=hur"]) self.assertEqual(result.output, "Config Error: No such rule 'foo'\n") self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) @patch("gitlint.cli.get_stdin_data", return_value=False) def test_target(self, _): """Test for the --target option""" with self.tempdir() as tmpdir: tmpdir_path = os.path.realpath(tmpdir) os.environ["LANGUAGE"] = "C" # Force language to english so we can check for error message result = self.cli.invoke(cli.cli, ["--target", tmpdir_path]) # We expect gitlint to tell us that /tmp is not a git repo (this proves that it takes the target parameter # into account). self.assertEqual(result.output, "%s is not a git repository.\n" % tmpdir_path) self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE) def test_target_negative(self): """Negative test for the --target option""" # try setting a non-existing target result = self.cli.invoke(cli.cli, ["--target", "/föo/bar"]) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) expected_msg = "Error: Invalid value for '--target': Directory '/föo/bar' does not exist." self.assertEqual(result.output.split("\n")[3], expected_msg) # try setting a file as target target_path = self.get_sample_path(os.path.join("config", "gitlintconfig")) result = self.cli.invoke(cli.cli, ["--target", target_path]) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) expected_msg = f"Error: Invalid value for '--target': Directory {target_path!r} is a file." self.assertEqual(result.output.split("\n")[3], expected_msg) @patch("gitlint.config.LintConfigGenerator.generate_config") def test_generate_config(self, generate_config): """Test for the generate-config subcommand""" result = self.cli.invoke(cli.cli, ["generate-config"], input="tëstfile\n") self.assertEqual(result.exit_code, self.GITLINT_SUCCESS_CODE) expected_msg = ( "Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + f"Successfully generated {os.path.realpath('tëstfile')}\n" ) self.assertEqual(result.output, expected_msg) generate_config.assert_called_once_with(os.path.realpath("tëstfile")) def test_generate_config_negative(self): """Negative test for the generate-config subcommand""" # Non-existing directory fake_dir = os.path.abspath("/föo") fake_path = os.path.join(fake_dir, "bar") result = self.cli.invoke(cli.cli, ["generate-config"], input=fake_path) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) expected_msg = ( f"Please specify a location for the sample gitlint config file [.gitlint]: {fake_path}\n" + f"Error: Directory '{fake_dir}' does not exist.\n" ) self.assertEqual(result.output, expected_msg) # Existing file sample_path = self.get_sample_path(os.path.join("config", "gitlintconfig")) result = self.cli.invoke(cli.cli, ["generate-config"], input=sample_path) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) expected_msg = ( "Please specify a location for the sample gitlint " f"config file [.gitlint]: {sample_path}\n" f'Error: File "{sample_path}" already exists.\n' ) self.assertEqual(result.output, expected_msg) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_git_error(self, sh, _): """Tests that the cli handles git errors properly""" sh.git.side_effect = CommandNotFound("git") result = self.cli.invoke(cli.cli) self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE) @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_no_commits_in_range(self, sh, _): """Test for --commits with the specified range being empty.""" sh.git.side_effect = lambda *_args, **_kwargs: "" result = self.cli.invoke(cli.cli, ["--commits", "main...HEAD"]) self.assert_log_contains('DEBUG: gitlint.cli No commits in range "main...HEAD"') self.assertEqual(result.exit_code, self.GITLINT_SUCCESS_CODE) @patch("gitlint.cli.get_stdin_data", return_value="WIP: tëst tïtle") def test_named_rules(self, _): with patch("gitlint.display.stderr", new=StringIO()) as stderr: config_path = self.get_sample_path(os.path.join("config", "named-rules")) result = self.cli.invoke(cli.cli, ["--config", config_path, "--debug"]) self.assertEqual(result.output, "") self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_named_rules_1")) self.assertEqual(result.exit_code, 4) # Assert debug logs are correct expected_kwargs = self.get_system_info_dict() expected_kwargs.update({"config_path": config_path}) expected_logs = self.get_expected("cli/test_cli/test_named_rules_2", expected_kwargs) self.assert_logged(expected_logs)