示例#1
0
class CLITests(BaseTestCase):
    CONFIG_ERROR_CODE = 10000

    def setUp(self):
        self.cli = CliRunner()

    def assert_output_line(self, output, index, sample_filename, error_line, expected_error):
        expected_output = "{0}:{1}: {2}".format(self.get_sample_path(sample_filename), error_line, expected_error)
        self.assertEqual(output.split("\n")[index], expected_output)

    def test_version(self):
        result = self.cli.invoke(cli.cli, ["--version"])
        self.assertEqual(result.output.split("\n")[0], "cli, version {0}".format(__version__))

    @patch('gitlint.cli.GitLinter')
    def test_config_file(self, git_linter):
        config_path = self.get_sample_path("config/gitlintconfig")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, "Using config from {}\n".format(config_path))

    def test_config_file_negative(self):
        config_path = self.get_sample_path("foo")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        expected_string = "Error: Invalid value for \"-C\" / \"--config\": Path \"{0}\" does not exist.".format(
            config_path)
        self.assertEqual(result.output.split("\n")[2], expected_string)

    def test_input_stream(self):
        expected_output = "1: T2 Title has trailing whitespace: \"WIP: title \"\n" + \
                          "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: title \"\n" + \
                          "3: B6 Body message is missing\n"

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, input='WIP: title \n')
            self.assertEqual(stderr.getvalue(), expected_output)
            self.assertEqual(result.exit_code, 3)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    def test_install_hook(self, install_hook):
        result = self.cli.invoke(cli.cli, ["--install-hook"])
        expected = "Successfully installed gitlint commit-msg hook in {}\n\n".format(hooks.COMMIT_MSG_HOOK_DST_PATH)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        install_hook.assert_called_once_with()

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook', side_effect=hooks.GitHookInstallerError("test"))
    def test_install_hook_negative(self, install_hook):
        result = self.cli.invoke(cli.cli, ["--install-hook"])
        self.assertEqual(result.exit_code, 1)
        self.assertEqual(result.output, "test\n")
        install_hook.assert_called_once_with()
示例#2
0
class CLITests(BaseTestCase):
    USAGE_ERROR_CODE = 253
    GIT_CONTEXT_ERROR_CODE = 254
    CONFIG_ERROR_CODE = 255

    def setUp(self):
        super(CLITests, self).setUp()
        self.cli = CliRunner()

    def test_version(self):
        """ Test for --version option """
        result = self.cli.invoke(cli.cli, ["--version"])
        self.assertEqual(
            result.output.split("\n")[0],
            "cli, version {0}".format(__version__))

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_lint(self, sys, sh):
        """ Test for basic simple linting functionality """
        sys.stdin.isatty.return_value = True

        def git_log_side_effect(*_args, **_kwargs):
            return (
                u"test åuthor,test-email@föo.com,2016-12-03 15:28:15 01:00,åbc\n"
                u"commït-title\n\ncommït-body")

        sh.git.log.side_effect = git_log_side_effect
        sh.git.side_effect = [
            "6f29bf81a8322a04071bb794666e48c443a90360",
            u"file1.txt\npåth/to/file2.txt\n"
        ]

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli)
            self.assertEqual(
                stderr.getvalue(),
                u'3: B5 Body message is too short (11<20): "commït-body"\n')
            self.assertEqual(result.exit_code, 1)

        # Make sure gitlint captured the correct logs
        expected_logs = [
            "DEBUG: gitlint.lint Linting commit 6f29bf81a8322a04071bb794666e48c443a90360",
            "DEBUG: gitlint.cli Exit Code = 1"
        ]
        self.assert_logged(expected_logs)

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_lint_multiple_commits(self, sys, sh):
        """ Test for --commits option """
        sys.stdin.isatty.return_value = True

        sh.git.log.side_effect = [
            u"test åuthor1,test-email1@föo.com,2016-12-03 15:28:15 01:00,åbc\n"
            u"commït-title1\n\ncommït-body1",
            u"test åuthor2,test-email3@föo.com,2016-12-04 15:28:15 01:00,åbc\n"
            u"commït-title2\n\ncommït-body2",
            u"test åuthor3,test-email3@föo.com,2016-12-05 15:28:15 01:00,åbc\n"
            u"commït-title3\n\ncommït-body3",
        ]
        sh.git.side_effect = [
            "6f29bf81a8322a04071bb794666e48c443a90360\n" +
            "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
            "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
            u"file1.txt\npåth/to/file2.txt\n",
            u"file4.txt\npåth/to/file5.txt\n",
            u"file6.txt\npåth/to/file7.txt\n"
        ]

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli)
            expected = (
                u"Commit 6f29bf81a8:\n"
                u'3: B5 Body message is too short (12<20): "commït-body1"\n\n'
                u"Commit 25053ccec5:\n"
                u'3: B5 Body message is too short (12<20): "commït-body2"\n\n'
                u"Commit 4da2656b0d:\n"
                u'3: B5 Body message is too short (12<20): "commït-body3"\n')
            self.assertEqual(stderr.getvalue(), expected)
            self.assertEqual(result.exit_code, 3)

            # Make sure gitlint captured the correct logs
            expected_logs = [
                'DEBUG: gitlint.lint Linting commit 6f29bf81a8322a04071bb794666e48c443a90360',
                'DEBUG: gitlint.lint Linting commit 25053ccec5e28e1bb8f7551fdbb5ab213ada2401',
                'DEBUG: gitlint.lint Linting commit 4da2656b0dadc76c7ee3fd0243a96cb64007f125',
                'DEBUG: gitlint.cli Exit Code = 3'
            ]
            self.assert_logged(expected_logs)

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_lint_multiple_commits_config(self, sys, sh):
        """ Test for --commits option where some of the commits have gitlint config in the commit message """
        sys.stdin.isatty.return_value = True

        # Note that the second commit title has a trailing period that is being ignored by gitlint-ignore: T3
        sh.git.log.side_effect = [
            u"test åuthor1,test-email1@föo.com,2016-12-03 15:28:15 01:00,åbc\n"
            u"commït-title1\n\ncommït-body1",
            u"test åuthor2,test-email3@föo.com,2016-12-04 15:28:15 01:00,åbc\n"
            u"commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n",
            u"test åuthor3,test-email3@föo.com,2016-12-05 15:28:15 01:00,åbc\n"
            u"commït-title3\n\ncommït-body3",
        ]
        sh.git.side_effect = [
            "6f29bf81a8322a04071bb794666e48c443a90360\n" +
            "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
            "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
            u"file1.txt\npåth/to/file2.txt\n",
            u"file4.txt\npåth/to/file5.txt\n",
            u"file6.txt\npåth/to/file7.txt\n"
        ]

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli)
            # We expect that the second commit has no failures because of 'gitlint-ignore: T3' in its commit msg body
            expected = (
                u"Commit 6f29bf81a8:\n"
                u'3: B5 Body message is too short (12<20): "commït-body1"\n\n'
                u"Commit 4da2656b0d:\n"
                u'3: B5 Body message is too short (12<20): "commït-body3"\n')
            self.assertEqual(stderr.getvalue(), expected)
            self.assertEqual(result.exit_code, 2)

    def test_input_stream(self):
        """ Test for linting when a message is passed via stdin """
        expected_output = u"1: T2 Title has trailing whitespace: \"WIP: tïtle \"\n" + \
                          u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: tïtle \"\n" + \
                          u"3: B6 Body message is missing\n"

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, input=u'WIP: tïtle \n')
            self.assertEqual(stderr.getvalue(), expected_output)
            self.assertEqual(result.exit_code, 3)
            self.assertEqual(result.output, "")

    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"],
                                     input=u"WIP: tïtle \n")
            self.assertEqual(stderr.getvalue(), "")
            self.assertEqual(result.exit_code, 3)
            self.assertEqual(result.output, "")

    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"], input=u"WIP: tïtle \n")
            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=u"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=u'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"
            )

    def test_debug(self):
        """ Test for --debug option """
        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            config_path = self.get_sample_path("config/gitlintconfig")
            result = self.cli.invoke(cli.cli,
                                     ["--config", config_path, "--debug"],
                                     input=u"WIP: tëst")
            expected = self.get_expected('debug_output1', {
                'config_path': config_path,
                'target': os.path.abspath(os.getcwd())
            })

            self.assertEqual(result.output, expected)
            self.assertEqual(stderr.getvalue(), "1: T5\n3: B6\n")
            self.assertEqual(result.exit_code, 2)

        # Make sure gitlint captured the correct logs
        expected_logs = [
            'DEBUG: gitlint.lint Linting commit [SHA UNKNOWN]',
            'DEBUG: gitlint.cli Exit Code = 2'
        ]
        self.assert_logged(expected_logs)

    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, "--debug"],
                                     input=u"Test tïtle\n")
            expected_output = u"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("user_rules/my_commit_rules.py")
            result = self.cli.invoke(cli.cli,
                                     ["--extra-path", extra_path, "--debug"],
                                     input=u"Test tïtle\n")
            expected_output = u"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)

    def test_config_file(self):
        """ Test for --config option """
        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            config_path = self.get_sample_path("config/gitlintconfig")
            result = self.cli.invoke(cli.cli, ["--config", config_path],
                                     input=u"WIP: tëst")
            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 = u"Error: Invalid value for \"-C\" / \"--config\": Path \"{0}\" is a directory.".format(
            config_path)
        self.assertEqual(result.output.split("\n")[2], expected_string)
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)

        # Non existing file
        config_path = self.get_sample_path(u"föo")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        expected_string = u"Error: Invalid value for \"-C\" / \"--config\": Path \"{0}\" does not exist.".format(
            config_path)
        self.assertEqual(result.output.split("\n")[2], expected_string)
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)

        # Invalid config file
        config_path = self.get_sample_path("config/invalid-option-value")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)

    @patch('gitlint.cli.sys')
    def test_target(self, sys):
        """ Test for the --target option """
        sys.stdin.isatty.return_value = True
        result = self.cli.invoke(cli.cli, ["--target", "/tmp"])
        # 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.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        expected_path = os.path.realpath("/tmp")
        self.assertEqual(result.output,
                         "%s is not a git repository.\n" % expected_path)

    def test_target_negative(self):
        """ Negative test for the --target option """
        # try setting a non-existing target
        result = self.cli.invoke(cli.cli, ["--target", u"/föo/bar"])
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"Error: Invalid value for \"--target\": Directory \"/föo/bar\" does not exist."
        self.assertEqual(result.output.split("\n")[2], expected_msg)

        # try setting a file as target
        target_path = self.get_sample_path("config/gitlintconfig")
        result = self.cli.invoke(cli.cli, ["--target", target_path])
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"Error: Invalid value for \"--target\": Directory \"{0}\" is a file.".format(
            target_path)
        self.assertEqual(result.output.split("\n")[2], 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=u"tëstfile\n")
        self.assertEqual(result.exit_code, 0)
        expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \
                       u"Successfully generated {0}\n".format(os.path.abspath(u"tëstfile"))
        self.assertEqual(result.output, expected_msg)
        generate_config.assert_called_once_with(os.path.abspath(u"tëstfile"))

    def test_generate_config_negative(self):
        """ Negative test for the generate-config subcommand """
        # Non-existing directory
        result = self.cli.invoke(cli.cli, ["generate-config"],
                                 input=u"/föo/bar")
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: /föo/bar\n" + \
                       u"Error: Directory '/föo' does not exist.\n"
        self.assertEqual(result.output, expected_msg)

        # Existing file
        sample_path = self.get_sample_path("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 " + \
                       "config file [.gitlint]: {0}\n".format(sample_path) + \
                       "Error: File \"{0}\" already exists.\n".format(sample_path)
        self.assertEqual(result.output, expected_msg)

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_git_error(self, sys, sh):
        """ Tests that the cli handles git errors properly """
        sys.stdin.isatty.return_value = True
        sh.git.side_effect = CommandNotFound("git")
        result = self.cli.invoke(cli.cli)
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_no_commits_in_range(self, sys, sh):
        """ Test for --commits with the specified range being empty. """
        sys.stdin.isatty.return_value = True
        sh.git.side_effect = lambda *_args, **_kwargs: ""
        result = self.cli.invoke(cli.cli, ["--commits", "master...HEAD"])
        expected = u'No commits in range "master...HEAD".\n'
        self.assertEqual(result.output, expected)
        self.assertEqual(result.exit_code, 0)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    def test_install_hook(self, install_hook):
        """ Test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        expected_path = os.path.join(os.getcwd(),
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = "Successfully installed gitlint commit-msg hook in {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    def test_install_hook_target(self, install_hook):
        """  Test for install-hook subcommand with a specific --target option specified """
        # Specified target
        result = self.cli.invoke(
            cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
        expected_path = os.path.realpath(
            os.path.join(self.SAMPLES_DIR, hooks.COMMIT_MSG_HOOK_DST_PATH))
        expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)

        expected_config = config.LintConfig()
        expected_config.target = self.SAMPLES_DIR
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_install_hook_negative(self, install_hook):
        """ Negative test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
    def test_uninstall_hook(self, uninstall_hook):
        """ Test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        expected_path = os.path.realpath(
            os.path.join(os.getcwd(), hooks.COMMIT_MSG_HOOK_DST_PATH))
        expected = "Successfully uninstalled gitlint commit-msg hook from {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_uninstall_hook_negative(self, uninstall_hook):
        """ Negative test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)
示例#3
0
class CLITests(BaseTestCase):
    USAGE_ERROR_CODE = 253
    GIT_CONTEXT_ERROR_CODE = 254
    CONFIG_ERROR_CODE = 255

    def setUp(self):
        self.cli = CliRunner()

    def assert_output_line(self, output, index, sample_filename, error_line,
                           expected_error):
        expected_output = "{0}:{1}: {2}".format(
            self.get_sample_path(sample_filename), error_line, expected_error)
        self.assertEqual(output.split("\n")[index], expected_output)

    def test_version(self):
        result = self.cli.invoke(cli.cli, ["--version"])
        self.assertEqual(
            result.output.split("\n")[0],
            "cli, version {0}".format(__version__))

    @patch('gitlint.cli.GitLinter')
    def test_config_file(self, _git_linter):
        config_path = self.get_sample_path("config/gitlintconfig")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, "")

    @patch('gitlint.cli.GitLinter')
    def test_config_file_debug(self, _git_linter):
        config_path = self.get_sample_path("config/gitlintconfig")
        result = self.cli.invoke(cli.cli, ["--config", config_path, "--debug"])
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output,
                         "Using config from {0}\n".format(config_path))

    def test_config_file_negative(self):
        # Directory as config file
        config_path = self.get_sample_path("config")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        expected_string = "Error: Invalid value for \"-C\" / \"--config\": Path \"{0}\" is a directory.".format(
            config_path)
        self.assertEqual(result.output.split("\n")[2], expected_string)
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)

        # Non existing file
        config_path = self.get_sample_path("foo")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        expected_string = "Error: Invalid value for \"-C\" / \"--config\": Path \"{0}\" does not exist.".format(
            config_path)
        self.assertEqual(result.output.split("\n")[2], expected_string)
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)

        # Invalid config file
        config_path = self.get_sample_path("config/invalid-option-value")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)

    @patch('gitlint.cli.sys')
    def test_target(self, sys):
        sys.stdin.isatty.return_value = True
        result = self.cli.invoke(cli.cli, ["--target", "/tmp"])
        # 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.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        expected_path = os.path.realpath("/tmp")
        self.assertEqual(result.output,
                         "%s is not a git repository.\n" % expected_path)

    def test_target_negative(self):
        # try setting a non-existing target
        result = self.cli.invoke(cli.cli, ["--target", "/foo/bar"])
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = "Error: Invalid value for \"--target\": Directory \"/foo/bar\" does not exist."
        self.assertEqual(result.output.split("\n")[2], expected_msg)

        # try setting a file as target
        target_path = self.get_sample_path("config/gitlintconfig")
        result = self.cli.invoke(cli.cli, ["--target", target_path])
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = "Error: Invalid value for \"--target\": Directory \"{0}\" is a file.".format(
            target_path)
        self.assertEqual(result.output.split("\n")[2], expected_msg)

    @patch('gitlint.config.LintConfigGenerator.generate_config')
    def test_generate_config(self, generate_config):
        result = self.cli.invoke(cli.cli, ["generate-config"],
                                 input="testfile\n")
        self.assertEqual(result.exit_code, 0)
        expected_msg = "Please specify a location for the sample gitlint config file [.gitlint]: testfile\n" + \
                       "Successfully generated {0}\n".format(os.path.abspath("testfile"))
        self.assertEqual(result.output, expected_msg)
        generate_config.assert_called_once_with(os.path.abspath("testfile"))

    def test_generate_config_negative(self):
        # Non-existing directory
        result = self.cli.invoke(cli.cli, ["generate-config"],
                                 input="/foo/bar")
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = "Please specify a location for the sample gitlint config file [.gitlint]: /foo/bar\n" + \
                       "Error: Directory '/foo' does not exist.\n"
        self.assertEqual(result.output, expected_msg)

        # Existing file
        sample_path = self.get_sample_path("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 " + \
                       "config file [.gitlint]: {0}\n".format(sample_path) + \
                       "Error: File \"{0}\" already exists.\n".format(sample_path)
        self.assertEqual(result.output, expected_msg)

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_git_error(self, sys, sh):
        sys.stdin.isatty.return_value = True
        sh.git.log.side_effect = CommandNotFound("git")
        result = self.cli.invoke(cli.cli)
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)

    def test_input_stream(self):
        expected_output = "1: T2 Title has trailing whitespace: \"WIP: title \"\n" + \
                          "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: title \"\n" + \
                          "3: B6 Body message is missing\n"

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, input='WIP: title \n')
            self.assertEqual(stderr.getvalue(), expected_output)
            self.assertEqual(result.exit_code, 3)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    def test_install_hook(self, install_hook):
        result = self.cli.invoke(cli.cli, ["install-hook"])
        expected_path = os.path.join(os.getcwd(),
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = "Successfully installed gitlint commit-msg hook in {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        install_hook.assert_called_once_with(config.LintConfig())

        # Specified target
        install_hook.reset_mock()
        result = self.cli.invoke(cli.cli, ["--target", "/tmp", "install-hook"])
        expected_path = os.path.realpath("/tmp/.git/hooks/commit-msg")
        expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        install_hook.assert_called_once_with(
            config.LintConfig(target=os.path.realpath("/tmp")))

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError("test"))
    def test_install_hook_negative(self, install_hook):
        result = self.cli.invoke(cli.cli, ["install-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, "test\n")
        install_hook.assert_called_once_with(config.LintConfig())

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
    def test_uninstall_hook(self, uninstall_hook):
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        expected_path = os.path.realpath(
            os.path.join(os.getcwd(), hooks.COMMIT_MSG_HOOK_DST_PATH))
        expected = "Successfully uninstalled gitlint commit-msg hook from {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        uninstall_hook.assert_called_once_with(config.LintConfig())

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError("test"))
    def test_uninstall_hook_negative(self, uninstall_hook):
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, "test\n")
        uninstall_hook.assert_called_once_with(config.LintConfig())
示例#4
0
class CLITests(BaseTestCase):
    USAGE_ERROR_CODE = 253
    GIT_CONTEXT_ERROR_CODE = 254
    CONFIG_ERROR_CODE = 255

    def setUp(self):
        self.cli = CliRunner()

    def test_version(self):
        result = self.cli.invoke(cli.cli, ["--version"])
        self.assertEqual(
            result.output.split("\n")[0],
            "cli, version {0}".format(__version__))

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_lint(self, sys, sh):
        sys.stdin.isatty.return_value = True

        def git_log_side_effect(*_args, **_kwargs):
            return (
                u"test åuthor,test-email@föo.com,2016-12-03 15:28:15 01:00,åbc\n"
                u"commït-title\n\ncommït-body")

        sh.git.log.side_effect = git_log_side_effect
        sh.git.side_effect = [
            "6f29bf81a8322a04071bb794666e48c443a90360",
            u"file1.txt\npåth/to/file2.txt\n"
        ]

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli)
            self.assertEqual(
                stderr.getvalue(),
                u'3: B5 Body message is too short (11<20): "commït-body"\n')
            self.assertEqual(result.exit_code, 1)

    def test_input_stream(self):
        expected_output = u"1: T2 Title has trailing whitespace: \"WIP: tïtle \"\n" + \
                          u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: tïtle \"\n" + \
                          u"3: B6 Body message is missing\n"

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, input=u'WIP: tïtle \n')
            self.assertEqual(stderr.getvalue(), expected_output)
            self.assertEqual(result.exit_code, 3)
            self.assertEqual(result.output, "")

    def test_silent_mode(self):
        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, ["--silent"],
                                     input=u"WIP: tïtle \n")
            self.assertEqual(stderr.getvalue(), "")
            self.assertEqual(result.exit_code, 3)
            self.assertEqual(result.output, "")

    def test_verbosity(self):
        # 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"], input=u"WIP: tïtle \n")
            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=u"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=u'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"
            )

    def test_debug(self):
        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            config_path = self.get_sample_path("config/gitlintconfig")
            result = self.cli.invoke(cli.cli,
                                     ["--config", config_path, "--debug"],
                                     input=u"WIP: tëst")
            expected = self.get_expected('debug_output1', {
                'config_path': config_path,
                'target': os.path.abspath(os.getcwd())
            })

            self.assertEqual(result.output, expected)
            self.assertEqual(stderr.getvalue(), "1: T5\n3: B6\n")
            self.assertEqual(result.exit_code, 2)

    def test_extra_path(self):
        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, "--debug"],
                                     input=u"Test tïtle\n")
            expected_output = u"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)

    def test_config_file(self):
        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            config_path = self.get_sample_path("config/gitlintconfig")
            result = self.cli.invoke(cli.cli, ["--config", config_path],
                                     input=u"WIP: tëst")
            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):
        # Directory as config file
        config_path = self.get_sample_path("config")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        expected_string = u"Error: Invalid value for \"-C\" / \"--config\": Path \"{0}\" is a directory.".format(
            config_path)
        self.assertEqual(result.output.split("\n")[2], expected_string)
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)

        # Non existing file
        config_path = self.get_sample_path(u"föo")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        expected_string = u"Error: Invalid value for \"-C\" / \"--config\": Path \"{0}\" does not exist.".format(
            config_path)
        self.assertEqual(result.output.split("\n")[2], expected_string)
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)

        # Invalid config file
        config_path = self.get_sample_path("config/invalid-option-value")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)

    @patch('gitlint.cli.sys')
    def test_target(self, sys):
        sys.stdin.isatty.return_value = True
        result = self.cli.invoke(cli.cli, ["--target", "/tmp"])
        # 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.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        expected_path = os.path.realpath("/tmp")
        self.assertEqual(result.output,
                         "%s is not a git repository.\n" % expected_path)

    def test_target_negative(self):
        # try setting a non-existing target
        result = self.cli.invoke(cli.cli, ["--target", u"/föo/bar"])
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"Error: Invalid value for \"--target\": Directory \"/föo/bar\" does not exist."
        self.assertEqual(result.output.split("\n")[2], expected_msg)

        # try setting a file as target
        target_path = self.get_sample_path("config/gitlintconfig")
        result = self.cli.invoke(cli.cli, ["--target", target_path])
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"Error: Invalid value for \"--target\": Directory \"{0}\" is a file.".format(
            target_path)
        self.assertEqual(result.output.split("\n")[2], expected_msg)

    @patch('gitlint.config.LintConfigGenerator.generate_config')
    def test_generate_config(self, generate_config):
        result = self.cli.invoke(cli.cli, ["generate-config"],
                                 input=u"tëstfile\n")
        self.assertEqual(result.exit_code, 0)
        expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \
                       u"Successfully generated {0}\n".format(os.path.abspath(u"tëstfile"))
        self.assertEqual(result.output, expected_msg)
        generate_config.assert_called_once_with(os.path.abspath(u"tëstfile"))

    def test_generate_config_negative(self):
        # Non-existing directory
        result = self.cli.invoke(cli.cli, ["generate-config"],
                                 input=u"/föo/bar")
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: /föo/bar\n" + \
                       u"Error: Directory '/föo' does not exist.\n"
        self.assertEqual(result.output, expected_msg)

        # Existing file
        sample_path = self.get_sample_path("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 " + \
                       "config file [.gitlint]: {0}\n".format(sample_path) + \
                       "Error: File \"{0}\" already exists.\n".format(sample_path)
        self.assertEqual(result.output, expected_msg)

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_git_error(self, sys, sh):
        sys.stdin.isatty.return_value = True
        sh.git.side_effect = CommandNotFound("git")
        result = self.cli.invoke(cli.cli)
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)

    @patch('gitlint.git.sh')
    @patch('gitlint.cli.sys')
    def test_no_commits_in_range(self, sys, sh):
        sys.stdin.isatty.return_value = True
        sh.git.side_effect = lambda *_args, **_kwargs: ""
        result = self.cli.invoke(cli.cli, ["--commits", "master...HEAD"])
        expected = u'No commits in range "master...HEAD".\n'
        self.assertEqual(result.output, expected)
        self.assertEqual(result.exit_code, 0)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    def test_install_hook(self, install_hook):
        result = self.cli.invoke(cli.cli, ["install-hook"])
        expected_path = os.path.join(os.getcwd(),
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = "Successfully installed gitlint commit-msg hook in {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    def test_install_hook_target(self, install_hook):
        # Specified target
        result = self.cli.invoke(
            cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
        expected_path = os.path.realpath(
            os.path.join(self.SAMPLES_DIR, hooks.COMMIT_MSG_HOOK_DST_PATH))
        expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)

        expected_config = config.LintConfig()
        expected_config.target = self.SAMPLES_DIR
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_install_hook_negative(self, install_hook):
        result = self.cli.invoke(cli.cli, ["install-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
    def test_uninstall_hook(self, uninstall_hook):
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        expected_path = os.path.realpath(
            os.path.join(os.getcwd(), hooks.COMMIT_MSG_HOOK_DST_PATH))
        expected = "Successfully uninstalled gitlint commit-msg hook from {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_uninstall_hook_negative(self, uninstall_hook):
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.abspath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)
示例#5
0
class CLIHookTests(BaseTestCase):
    USAGE_ERROR_CODE = 253
    GIT_CONTEXT_ERROR_CODE = 254
    CONFIG_ERROR_CODE = 255

    def setUp(self):
        super(CLIHookTests, self).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()

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_install_hook(self, _, install_hook):
        """ Test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = u"Successfully installed gitlint commit-msg hook in {0}\n".format(
            expected_path)
        self.assertEqual(result.output, expected)
        self.assertEqual(result.exit_code, 0)
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_install_hook_target(self, _, install_hook):
        """  Test for install-hook subcommand with a specific --target option specified """
        # Specified target
        result = self.cli.invoke(
            cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)

        expected_config = config.LintConfig()
        expected_config.target = self.SAMPLES_DIR
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_install_hook_negative(self, install_hook):
        """ Negative test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_uninstall_hook(self, _, uninstall_hook):
        """ Test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = u"Successfully uninstalled gitlint commit-msg hook from {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_uninstall_hook_negative(self, uninstall_hook):
        """ Negative test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)
示例#6
0
class CLITests(BaseTestCase):
    USAGE_ERROR_CODE = 253
    GIT_CONTEXT_ERROR_CODE = 254
    CONFIG_ERROR_CODE = 255

    def setUp(self):
        super(CLITests, self).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()

    def test_version(self):
        """ Test for --version option """
        result = self.cli.invoke(cli.cli, ["--version"])
        self.assertEqual(
            result.output.split("\n")[0],
            "cli, version {0}".format(__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 = self.GIT_CONFIG_SIDE_EFFECTS + [
            "6f29bf81a8322a04071bb794666e48c443a90360",
            u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 01:00\x00åbc\n"
            u"commït-title\n\ncommït-body", u"file1.txt\npåth/to/file2.txt\n"
        ]

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli)
            self.assertEqual(
                stderr.getvalue(),
                u'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 """

        sh.git.side_effect = self.GIT_CONFIG_SIDE_EFFECTS + [
            "6f29bf81a8322a04071bb794666e48c443a90360\n"
            +  # git rev-list <SHA>
            "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
            "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
            # git log --pretty <FORMAT> <SHA>
            u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 01 :00\x00åbc\n"
            u"commït-title1\n\ncommït-body1",
            u"file1.txt\npåth/to/file2.txt\n",  # git diff-tree <SHA>
            u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 01:00\x00åbc\n"
            u"commït-title2\n\ncommït-body2",
            u"file4.txt\npåth/to/file5.txt\n",
            u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 01:00\x00åbc\n"
            u"commït-title3\n\ncommït-body3",
            u"file6.txt\npåth/to/file7.txt\n"
        ]

        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("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_config(self, sh, _):
        """ Test for --commits option where some of the commits have gitlint config in the commit message """

        # Note that the second commit title has a trailing period that is being ignored by gitlint-ignore: T3
        sh.git.side_effect = self.GIT_CONFIG_SIDE_EFFECTS + [
            "6f29bf81a8322a04071bb794666e48c443a90360\n"
            +  # git rev-list <SHA>
            "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
            "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
            # git log --pretty <FORMAT> <SHA>
            u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 01:00\x00åbc\n"
            u"commït-title1\n\ncommït-body1",
            u"file1.txt\npåth/to/file2.txt\n",  # git diff-tree <SHA>
            u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 01:00\x00åbc\n"
            u"commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n",
            u"file4.txt\npåth/to/file5.txt\n",
            u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 01:00\x00åbc\n"
            u"commït-title3.\n\ncommït-body3",
            u"file6.txt\npåth/to/file7.txt\n"
        ]

        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(
                    "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
        """

        # Note that the second commit
        sh.git.side_effect = self.GIT_CONFIG_SIDE_EFFECTS + [
            "6f29bf81a8322a04071bb794666e48c443a90360\n"
            +  # git rev-list <SHA>
            "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
            "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
            # git log --pretty <FORMAT> <SHA>
            u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 01:00\x00åbc\n"
            u"commït-title1\n\ncommït-body1",
            u"file1.txt\npåth/to/file2.txt\n",  # git diff-tree <SHA>
            u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 01:00\x00åbc\n"
            # Normally T3 violation (trailing punctuation), but this commit is ignored because of
            # config below
            u"commït-title2.\n\ncommït-body2\n",
            u"file4.txt\npåth/to/file5.txt\n",
            u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 01:00\x00åbc\n"
            # Normally T1 and B5 violations, now only T1 because we're ignoring B5 in config below
            u"commït-title3.\n\ncommït-body3 foo",
            u"file6.txt\npåth/to/file7.txt\n"
        ]

        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 = (
                u"Commit 6f29bf81a8:\n"
                u'3: B5 Body message is too short (12<20): "commït-body1"\n\n'
                u"Commit 4da2656b0d:\n"
                u'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=u'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("test_cli/test_input_stream_1"))
            self.assertEqual(result.exit_code, 3)
            self.assertEqual(result.output, "")

    @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 = self.GIT_CONFIG_SIDE_EFFECTS + [
            "6f29bf81a8322a04071bb794666e48c443a90360",
            u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 01:00\x00åbc\n"
            u"commït-title\n\ncommït-body", u"file1.txt\npåth/to/file2.txt\n"
        ]

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, ["--ignore-stdin"])
            self.assertEqual(
                stderr.getvalue(),
                u'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=False)
    def test_msg_filename(self, _):
        expected_output = u"3: B6 Body message is missing\n"

        with tempdir() as tmpdir:
            msg_filename = os.path.join(tmpdir, "msg")
            with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
                f.write(u"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=u"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=u"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=u"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=u'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 """

        sh.git.side_effect = self.GIT_CONFIG_SIDE_EFFECTS + [
            "6f29bf81a8322a04071bb794666e48c443a90360\n"  # git rev-list <SHA>
            "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n"
            "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
            u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 01:00\x00abc\n"
            u"commït-title1\n\ncommït-body1",
            u"file1.txt\npåth/to/file2.txt\n",
            u"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 01:00\x00abc\n"
            u"commït-title2.\n\ncommït-body2",
            u"file4.txt\npåth/to/file5.txt\n",
            u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 01:00\x00abc\n"
            u"föo\nbar",
            u"file6.txt\npåth/to/file7.txt\n"
        ]

        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 = {
                'platform': platform.platform(),
                "python_version": sys.version,
                'gitlint_version': __version__,
                'config_path': config_path,
                'GITLINT_USE_SH_LIB': self.GITLINT_USE_SH_LIB,
                'target': os.path.realpath(os.getcwd())
            }
            expected_logs = self.get_expected('test_cli/test_debug_1',
                                              expected_kwargs)
            self.assert_logged(expected_logs)

    @patch('gitlint.cli.get_stdin_data', return_value=u"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, "--debug"])
            expected_output = u"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, "--debug"])
            expected_output = u"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=u"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('test_cli/test_contrib_1')
            self.assertEqual(stderr.getvalue(), expected_output)
            self.assertEqual(result.exit_code, 3)

    @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n")
    def test_contrib_negative(self, _):
        result = self.cli.invoke(cli.cli, ["--contrib", u"föobar,CC1"])
        self.assertEqual(
            result.output,
            u"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=u"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)

    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 = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" is a directory.".format(
            config_path)
        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(u"föo")
        result = self.cli.invoke(cli.cli, ["--config", config_path])
        expected_string = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" does not exist.".format(
            config_path)
        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)

    @patch('gitlint.cli.get_stdin_data', return_value=False)
    def test_target(self, _):
        """ Test for the --target option """
        os.environ[
            "LANGUAGE"] = "C"  # Force language to english so we can check for error message
        result = self.cli.invoke(cli.cli, ["--target", "/tmp"])
        # We expect gitlint to tell us that /tmp is not a git repo (this proves that it takes the target parameter
        # into account).
        expected_path = os.path.realpath("/tmp")
        self.assertEqual(result.output,
                         "%s is not a git repository.\n" % expected_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", u"/föo/bar"])
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"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 = u"Error: Invalid value for \"--target\": Directory \"{0}\" is a file.".format(
            target_path)
        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=u"tëstfile\n")
        self.assertEqual(result.exit_code, 0)
        expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \
                       u"Successfully generated {0}\n".format(os.path.realpath(u"tëstfile"))
        self.assertEqual(result.output, expected_msg)
        generate_config.assert_called_once_with(os.path.realpath(u"tëstfile"))

    def test_generate_config_negative(self):
        """ Negative test for the generate-config subcommand """
        # Non-existing directory
        result = self.cli.invoke(cli.cli, ["generate-config"],
                                 input=u"/föo/bar")
        self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
        expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: /föo/bar\n" + \
                       u"Error: Directory '/föo' 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 " + \
                       "config file [.gitlint]: {0}\n".format(sample_path) + \
                       "Error: File \"{0}\" already exists.\n".format(sample_path)
        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", "master...HEAD"])

        self.assert_log_contains(
            u"DEBUG: gitlint.cli No commits in range \"master...HEAD\"")
        self.assertEqual(result.exit_code, 0)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_install_hook(self, _, install_hook):
        """ Test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = u"Successfully installed gitlint commit-msg hook in {0}\n".format(
            expected_path)
        self.assertEqual(result.output, expected)
        self.assertEqual(result.exit_code, 0)
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_install_hook_target(self, _, install_hook):
        """  Test for install-hook subcommand with a specific --target option specified """
        # Specified target
        result = self.cli.invoke(
            cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)

        expected_config = config.LintConfig()
        expected_config.target = self.SAMPLES_DIR
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_install_hook_negative(self, install_hook):
        """ Negative test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_uninstall_hook(self, _, uninstall_hook):
        """ Test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = u"Successfully uninstalled gitlint commit-msg hook from {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_uninstall_hook_negative(self, uninstall_hook):
        """ Negative test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)
示例#7
0
class CLIHookTests(BaseTestCase):
    USAGE_ERROR_CODE = 253
    GIT_CONTEXT_ERROR_CODE = 254
    CONFIG_ERROR_CODE = 255

    def setUp(self):
        super(CLIHookTests, self).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()

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_install_hook(self, _, install_hook):
        """ Test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = u"Successfully installed gitlint commit-msg hook in {0}\n".format(
            expected_path)
        self.assertEqual(result.output, expected)
        self.assertEqual(result.exit_code, 0)
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_install_hook_target(self, _, install_hook):
        """  Test for install-hook subcommand with a specific --target option specified """
        # Specified target
        result = self.cli.invoke(
            cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)

        expected_config = config.LintConfig()
        expected_config.target = self.SAMPLES_DIR
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_install_hook_negative(self, install_hook):
        """ Negative test for install-hook subcommand """
        result = self.cli.invoke(cli.cli, ["install-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        install_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
    @patch('gitlint.hooks.git_hooks_dir',
           return_value=os.path.join(u"/hür", u"dur"))
    def test_uninstall_hook(self, _, uninstall_hook):
        """ Test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        expected_path = os.path.join(u"/hür", u"dur",
                                     hooks.COMMIT_MSG_HOOK_DST_PATH)
        expected = u"Successfully uninstalled gitlint commit-msg hook from {0}\n".format(
            expected_path)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output, expected)
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)

    @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook',
           side_effect=hooks.GitHookInstallerError(u"tëst"))
    def test_uninstall_hook_negative(self, uninstall_hook):
        """ Negative test for uninstall-hook subcommand """
        result = self.cli.invoke(cli.cli, ["uninstall-hook"])
        self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
        self.assertEqual(result.output, u"tëst\n")
        expected_config = config.LintConfig()
        expected_config.target = os.path.realpath(os.getcwd())
        uninstall_hook.assert_called_once_with(expected_config)

    def test_hook_no_tty(self):
        """ Test for run-hook subcommand.
            When no TTY is available (like is the case for this test), the hook will abort after the first check.
        """

        # No need to patch git as we're passing a msg-filename to run-hook, so no git calls are made.
        # Note that this is the case when passing --staged as well, but that's tested as part of the integration tests
        # (=end-to-end scenario).

        # Ideally we'd be able to assert that run-hook internally calls the lint cli command, but couldn't make
        # that work. Have tried many different variatons of mocking and patching without avail. For now, we just
        # check the output which indirectly proves the same thing.

        with self.tempdir() as tmpdir:
            msg_filename = os.path.join(tmpdir, u"hür")
            with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
                f.write(u"WIP: tïtle\n")

            with patch('gitlint.display.stderr', new=StringIO()) as stderr:
                result = self.cli.invoke(
                    cli.cli, ["--msg-filename", msg_filename, "run-hook"])
                self.assertEqual(
                    result.output,
                    self.get_expected(
                        'test_cli_hooks/test_hook_no_tty_1_stdout'))
                self.assertEqual(
                    stderr.getvalue(),
                    self.get_expected(
                        "test_cli_hooks/test_hook_no_tty_1_stderr"))

                # exit code is 1 because aborted (no stdin available)
                self.assertEqual(result.exit_code, 1)

    @patch('gitlint.cli.shell')
    def test_hook_edit(self, shell):
        """ Test for run-hook subcommand, answering 'e(dit)' after commit-hook """

        set_editors = [None, u"myeditor"]
        expected_editors = [u"vim", u"myeditor"]
        commit_messages = [u"WIP: höok edit 1", u"WIP: höok edit 2"]

        for i in range(0, len(set_editors)):
            if set_editors[i]:
                os.environ['EDITOR'] = set_editors[i]

            with self.patch_input(['e', 'e', 'n']):
                with self.tempdir() as tmpdir:
                    msg_filename = os.path.realpath(
                        os.path.join(tmpdir, u"hür"))
                    with io.open(msg_filename, 'w',
                                 encoding=DEFAULT_ENCODING) as f:
                        f.write(commit_messages[i] + "\n")

                    with patch('gitlint.display.stderr',
                               new=StringIO()) as stderr:
                        result = self.cli.invoke(
                            cli.cli,
                            ["--msg-filename", msg_filename, "run-hook"])
                        self.assertEqual(
                            result.output,
                            self.get_expected(
                                'test_cli_hooks/test_hook_edit_1_stdout',
                                {"commit_msg": commit_messages[i]}))
                        self.assertEqual(
                            stderr.getvalue(),
                            self.get_expected(
                                "test_cli_hooks/test_hook_edit_1_stderr",
                                {"commit_msg": commit_messages[i]}))

                        # exit code = number of violations
                        self.assertEqual(result.exit_code, 2)

                        shell.assert_called_with(
                            [expected_editors[i], msg_filename])
                        self.assert_log_contains(
                            u"DEBUG: gitlint.cli run-hook: editing commit message"
                        )
                        self.assert_log_contains(
                            u"DEBUG: gitlint.cli run-hook: {0} {1}".format(
                                expected_editors[i], msg_filename))

    def test_hook_no(self):
        """ Test for run-hook subcommand, answering 'n(o)' after commit-hook """

        with self.patch_input(['n']):
            with self.tempdir() as tmpdir:
                msg_filename = os.path.join(tmpdir, u"hür")
                with io.open(msg_filename, 'w',
                             encoding=DEFAULT_ENCODING) as f:
                    f.write(u"WIP: höok no\n")

                with patch('gitlint.display.stderr', new=StringIO()) as stderr:
                    result = self.cli.invoke(
                        cli.cli, ["--msg-filename", msg_filename, "run-hook"])
                    self.assertEqual(
                        result.output,
                        self.get_expected(
                            'test_cli_hooks/test_hook_no_1_stdout'))
                    self.assertEqual(
                        stderr.getvalue(),
                        self.get_expected(
                            "test_cli_hooks/test_hook_no_1_stderr"))

                    # We decided not to keep the commit message: hook returns number of violations (>0)
                    # This will cause git to abort the commit
                    self.assertEqual(result.exit_code, 2)
                    self.assert_log_contains(
                        "DEBUG: gitlint.cli run-hook: commit message declined")

    def test_hook_yes(self):
        """ Test for run-hook subcommand, answering 'y(es)' after commit-hook """
        with self.patch_input(['y']):
            with self.tempdir() as tmpdir:
                msg_filename = os.path.join(tmpdir, u"hür")
                with io.open(msg_filename, 'w',
                             encoding=DEFAULT_ENCODING) as f:
                    f.write(u"WIP: höok yes\n")

                with patch('gitlint.display.stderr', new=StringIO()) as stderr:
                    result = self.cli.invoke(
                        cli.cli, ["--msg-filename", msg_filename, "run-hook"])
                    self.assertEqual(
                        result.output,
                        self.get_expected(
                            'test_cli_hooks/test_hook_yes_1_stdout'))
                    self.assertEqual(
                        stderr.getvalue(),
                        self.get_expected(
                            "test_cli_hooks/test_hook_yes_1_stderr"))

                    # Exit code is 0 because we decide to keep the commit message
                    # This will cause git to keep the commit
                    self.assertEqual(result.exit_code, 0)
                    self.assert_log_contains(
                        "DEBUG: gitlint.cli run-hook: commit message accepted")

    @patch('gitlint.cli.get_stdin_data',
           return_value=u"WIP: Test hook stdin tïtle\n")
    def test_hook_stdin(self, _):
        """ Test for passing stdin data to run-hook, equivalent of:
            $ echo "Test hook stdin tïtle" | gitlint run-hook
        """

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, ["run-hook"])
            self.assertEqual(
                stderr.getvalue(),
                self.get_expected('test_cli_hooks/test_hook_stdin_1_stderr'))
            self.assertEqual(
                result.output,
                self.get_expected('test_cli_hooks/test_hook_stdin_1_stdout'))
            # Hook will auto-abort because we're using stdin. Abort = exit code 1
            self.assertEqual(result.exit_code, 1)

    @patch('gitlint.cli.get_stdin_data',
           return_value=u"WIP: Test hook config tïtle\n")
    def test_hook_config(self, _):
        """ Test that gitlint still respects config when running run-hook, equivalent of:
            $ echo "Test hook stdin tïtle" | gitlint -c title-max-length.line-length=5 --ignore B6 run-hook
        """

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            result = self.cli.invoke(cli.cli, [
                "-c", "title-max-length.line-length=5", "--ignore", "B6",
                "run-hook"
            ])
            self.assertEqual(
                stderr.getvalue(),
                self.get_expected('test_cli_hooks/test_hook_config_1_stderr'))
            self.assertEqual(
                result.output,
                self.get_expected('test_cli_hooks/test_hook_config_1_stdout'))
            # Hook will auto-abort because we're using stdin. Abort = exit code 1
            self.assertEqual(result.exit_code, 1)

    @patch('gitlint.cli.get_stdin_data', return_value=False)
    @patch('gitlint.git.sh')
    def test_hook_local_commit(self, sh, _):
        """ Test running the hook on the last commit-msg from the local repo, equivalent of:
            $ gitlint run-hook
            and then choosing 'e'
        """
        sh.git.side_effect = [
            "6f29bf81a8322a04071bb794666e48c443a90360",
            u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
            u"WIP: commït-title\n\ncommït-body",
            u"#",  # git config --get core.commentchar
            u"commit-1-branch-1\ncommit-1-branch-2\n",
            u"file1.txt\npåth/to/file2.txt\n"
        ]

        with self.patch_input(['e']):
            with patch('gitlint.display.stderr', new=StringIO()) as stderr:
                result = self.cli.invoke(cli.cli, ["run-hook"])
                self.assertEqual(
                    stderr.getvalue(),
                    self.get_expected(
                        'test_cli_hooks/test_hook_local_commit_1_stderr'))
                self.assertEqual(
                    result.output,
                    self.get_expected(
                        'test_cli_hooks/test_hook_local_commit_1_stdout'))
                # If we can't edit the message, run-hook follows regular gitlint behavior and exit code = # violations
                self.assertEqual(result.exit_code, 2)