summaryrefslogtreecommitdiffstats
path: root/qa/test_gitlint.py
blob: 7a04a398e370d0e33bd6da4bd7bc086cc2937f86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
import os

from qa.base import BaseTestCase
from qa.shell import echo, git, gitlint
from qa.utils import FILE_ENCODING


class IntegrationTests(BaseTestCase):
    """Simple set of integration tests for gitlint"""

    def test_successful(self):
        # Test for STDIN with and without a TTY attached
        self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit")
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
        self.assertEqualStdout(output, "")

    def test_successful_gitconfig(self):
        """Test gitlint when the underlying repo has specific git config set.
        In the past, we've had issues with gitlint failing on some of these, so this acts as a regression test."""

        # Different commentchar (Note: tried setting this to a special unicode char, but git doesn't like that)
        git("config", "--add", "core.commentchar", "$", _cwd=self.tmp_git_repo)
        self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored")
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
        self.assertEqualStdout(output, "")

    def test_successful_merge_commit(self):
        # Create branch on main
        self.create_simple_commit("Cömmit on main\n\nSimple bödy")

        # Create test branch, add a commit and determine the commit hash
        git("checkout", "-b", "test-branch", _cwd=self.tmp_git_repo)
        git("checkout", "test-branch", _cwd=self.tmp_git_repo)
        commit_title = "Commit on test-brånch with a pretty long title that will cause issues when merging"
        self.create_simple_commit(f"{commit_title}\n\nSïmple body")
        hash = self.get_last_commit_hash()

        # Checkout main and merge the commit
        # We explicitly set the title of the merge commit to the title of the previous commit as this or similar
        # behavior is what many tools do that handle merges (like github, gerrit, etc).
        git("checkout", "main", _cwd=self.tmp_git_repo)
        git("merge", "--no-ff", "-m", f"Merge '{commit_title}'", hash, _cwd=self.tmp_git_repo)

        # Run gitlint and assert output is empty
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
        self.assertEqualStdout(output, "")

        # Assert that we do see the error if we disable the ignore-merge-commits option
        output = gitlint("-c", "general.ignore-merge-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
        self.assertEqual(output.exit_code, 1)
        self.assertEqualStdout(output, f"1: T1 Title exceeds max length (90>72): \"Merge '{commit_title}'\"\n")

    def test_fixup_commit(self):
        # Create a normal commit and assert that it has a violation
        test_filename = self.create_simple_commit("Cömmit on WIP main\n\nSimple bödy that is long enough")
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
        expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP main\"\n"
        self.assertEqualStdout(output, expected)

        # Make a small modification to the commit and commit it using fixup commit
        with open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=FILE_ENCODING) as fh:
            fh.write("Appending söme stuff\n")

        git("add", test_filename, _cwd=self.tmp_git_repo)

        git("commit", "--fixup", self.get_last_commit_hash(), _cwd=self.tmp_git_repo)

        # Assert that gitlint does not show an error for the fixup commit
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
        # No need to check exit code, the command above throws an exception on > 0 exit codes
        self.assertEqualStdout(output, "")

        # Make sure that if we set the ignore-fixup-commits option to false that we do still see the violations
        output = gitlint("-c", "general.ignore-fixup-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
        expected = (
            "1: T5 Title contains the word 'WIP' (case-insensitive): \"fixup! Cömmit on WIP main\"\n"
            "3: B6 Body message is missing\n"
        )

        self.assertEqualStdout(output, expected)

    def test_fixup_amend_commit(self):
        # Create a normal commit and assert that it has a violation
        test_filename = self.create_simple_commit("Cömmit on WIP main\n\nSimple bödy that is long enough")
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
        expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP main\"\n"
        self.assertEqualStdout(output, expected)

        # Make a small modification to the commit and commit it using fixup=amend commit
        with open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=FILE_ENCODING) as fh:
            fh.write("Appending söme stuff\n")

        git("add", test_filename, _cwd=self.tmp_git_repo)

        # We have to use --no-edit to avoid git starting $EDITOR to modify the commit message that is being amended
        git("commit", "--no-edit", f"--fixup=amend:{self.get_last_commit_hash()}", _cwd=self.tmp_git_repo)

        # Assert that gitlint does not show an error for the fixup commit
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
        # No need to check exit code, the command above throws an exception on > 0 exit codes
        self.assertEqualStdout(output, "")

        # Make sure that if we set the ignore-fixup-commits option to false that we do still see the violations
        output = gitlint(
            "-c", "general.ignore-fixup-amend-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]
        )
        expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"amend! Cömmit on WIP main\"\n"

        self.assertEqualStdout(output, expected)

    def test_revert_commit(self):
        self.create_simple_commit("WIP: Cömmit on main.\n\nSimple bödy")
        hash = self.get_last_commit_hash()
        git("revert", hash, _cwd=self.tmp_git_repo)

        # Run gitlint and assert output is empty
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
        self.assertEqualStdout(output, "")

        # Assert that we do see the error if we disable the ignore-revert-commits option
        output = gitlint(
            "-c", "general.ignore-revert-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]
        )
        self.assertEqual(output.exit_code, 1)
        expected = '1: T5 Title contains the word \'WIP\' (case-insensitive): "Revert "WIP: Cömmit on main.""\n'
        self.assertEqualStdout(output, expected)

    def test_squash_commit(self):
        # Create a normal commit and assert that it has a violation
        test_filename = self.create_simple_commit("Cömmit on WIP main\n\nSimple bödy that is long enough")
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
        expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP main\"\n"
        self.assertEqualStdout(output, expected)

        # Make a small modification to the commit and commit it using squash commit
        with open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=FILE_ENCODING) as fh:
            # Wanted to write a unicode string, but that's obnoxious if you want to do it across Python 2 and 3.
            # https://stackoverflow.com/questions/22392377/
            # error-writing-a-file-with-file-write-in-python-unicodeencodeerror
            # So just keeping it simple - ASCII will here
            fh.write("Appending some stuff\n")

        git("add", test_filename, _cwd=self.tmp_git_repo)

        git("commit", "--squash", self.get_last_commit_hash(), "-m", "Töo short body", _cwd=self.tmp_git_repo)

        # Assert that gitlint does not show an error for the fixup commit
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
        # No need to check exit code, the command above throws an exception on > 0 exit codes
        self.assertEqualStdout(output, "")

        # Make sure that if we set the ignore-squash-commits option to false that we do still see the violations
        output = gitlint(
            "-c", "general.ignore-squash-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2]
        )
        expected = (
            "1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP main\"\n"
            '3: B5 Body message is too short (14<20): "Töo short body"\n'
        )

        self.assertEqualStdout(output, expected)

    def test_violations(self):
        commit_msg = "WIP: This ïs a title.\nContent on the sëcond line"
        self.create_simple_commit(commit_msg)
        output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
        self.assertEqualStdout(output, self.get_expected("test_gitlint/test_violations_1"))

    def test_msg_filename(self):
        tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename test.")
        output = gitlint("--msg-filename", tmp_commit_msg_file, _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[3])
        self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_1"))

    def test_msg_filename_no_tty(self):
        """Make sure --msg-filename option also works with no TTY attached"""
        tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO TTY test.")

        # We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's
        # no TTY attached to STDIN
        # http://amoffat.github.io/sh/sections/special_arguments.html?highlight=_tty_in#err-to-out
        # We need to pass some whitespace to _in as sh will otherwise hang, see
        # https://github.com/amoffat/sh/issues/427
        output = gitlint(
            "--msg-filename",
            tmp_commit_msg_file,
            _cwd=self.tmp_git_repo,
            _in=" ",
            _tty_in=False,
            _err_to_out=True,
            _ok_code=[3],
        )

        self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_no_tty_1"))

    def test_no_git_name_set(self):
        """Ensure we print out a helpful message if user.name is not set"""
        tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO name test.")
        # Name is checked before email so this isn't strictly
        # necessary but seems good for consistency.
        env = self.create_tmp_git_config("[user]\n  email = test-emåil@foo.com\n")
        output = gitlint(
            "--staged",
            "--msg-filename",
            tmp_commit_msg_file,
            _ok_code=[self.GIT_CONTEXT_ERROR_CODE],
            _env=env,
            _cwd=self.tmp_git_repo,
        )
        expected = "Missing git configuration: please set user.name\n"
        self.assertEqualStdout(output, expected)

    def test_no_git_email_set(self):
        """Ensure we print out a helpful message if user.email is not set"""
        tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO email test.")
        env = self.create_tmp_git_config("[user]\n  name = test åuthor\n")
        output = gitlint(
            "--staged",
            "--msg-filename",
            tmp_commit_msg_file,
            _ok_code=[self.GIT_CONTEXT_ERROR_CODE],
            _env=env,
            _cwd=self.tmp_git_repo,
        )
        expected = "Missing git configuration: please set user.email\n"
        self.assertEqualStdout(output, expected)

    def test_git_empty_repo(self):
        # Repo has no commits: caused by `git log`
        empty_git_repo = self.create_tmp_git_repo()
        output = gitlint(_cwd=empty_git_repo, _tty_in=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE])

        expected = "Current branch has no commits. Gitlint requires at least one commit to function.\n"
        self.assertEqualStdout(output, expected)

    def test_git_empty_repo_staged(self):
        """When repo is empty, we can still use gitlint when using --staged flag and piping a message into it"""
        empty_git_repo = self.create_tmp_git_repo()
        expected = (
            '1: T3 Title has trailing punctuation (.): "WIP: Pïpe test."\n'
            "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: Pïpe test.\"\n"
            "3: B6 Body message is missing\n"
        )

        output = gitlint(
            echo("WIP: Pïpe test."), "--staged", _cwd=empty_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3]
        )
        self.assertEqualStdout(output, expected)

    def test_commit_binary_file(self):
        """When committing a binary file, git shows somewhat different output in diff commands,
        this test ensures gitlint deals with that correctly"""
        binary_filename = self.create_simple_commit("Sïmple commit", file_contents=bytes([0x48, 0x00, 0x49, 0x00]))
        output = gitlint(
            "--debug",
            _ok_code=[1],
            _cwd=self.tmp_git_repo,
        )

        expected_kwargs = self.get_debug_vars_last_commit()
        expected_kwargs.update(
            {
                "changed_files": [binary_filename],
                "changed_files_stats": (f"{binary_filename}: None additions, None deletions"),
            }
        )
        expected = self.get_expected("test_gitlint/test_commit_binary_file_1", expected_kwargs)
        self.assertEqualStdout(output, expected)