summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Vagrantfile13
-rw-r--r--gitlint/cli.py59
-rw-r--r--gitlint/tests/config/test_config_precedence.py22
-rw-r--r--gitlint/tests/test_cli.py44
-rw-r--r--qa/base.py39
-rw-r--r--qa/expected/debug_output11
-rw-r--r--qa/test_commits.py15
-rw-r--r--qa/test_config.py16
-rw-r--r--qa/test_gitlint.py64
-rw-r--r--qa/test_stdin.py63
-rw-r--r--qa/test_user_defined.py6
11 files changed, 238 insertions, 104 deletions
diff --git a/Vagrantfile b/Vagrantfile
index e550167..0939a09 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -19,14 +19,25 @@ grep 'cd /vagrant' /home/vagrant/.bashrc || echo 'cd /vagrant' >> /home/vagrant/
grep 'source .venv27/bin/activate' /home/vagrant/.bashrc || echo 'source .venv27/bin/activate' >> /home/vagrant/.bashrc
EOF
+INSTALL_JENKINS=<<EOF
+wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
+sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
+sudo apt-get update
+sudo apt-get install -y jenkins
+EOF
+
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/xenial64"
config.vm.define "dev" do |dev|
- dev.vm.provision "shell", inline: "#{INSTALL_DEPS}"
+ dev.vm.provision "gitlint", type: "shell", inline: "#{INSTALL_DEPS}"
+ # Use 'vagrant provision --provision-with jenkins' to only run jenkins install
+ dev.vm.provision "jenkins", type: "shell", inline: "#{INSTALL_JENKINS}"
end
+ config.vm.network "forwarded_port", guest: 8080, host: 9080
+
if Vagrant.has_plugin?("vagrant-cachier")
config.cache.scope = :box
end
diff --git a/gitlint/cli.py b/gitlint/cli.py
index 8f57090..1d22bf2 100644
--- a/gitlint/cli.py
+++ b/gitlint/cli.py
@@ -4,9 +4,8 @@ import copy
import logging
import os
import platform
-import select
+import stat
import sys
-
import click
# Error codes
@@ -94,17 +93,36 @@ def build_config(ctx, target, config_path, c, extra_path, ignore, verbose, silen
ctx.exit(CONFIG_ERROR_CODE) # return CONFIG_ERROR_CODE on config error
-def stdin_has_data():
- """ Helper function that indicates whether the stdin has data incoming or not """
- # This code was taken from:
- # https://stackoverflow.com/questions/3762881/how-do-i-check-if-stdin-has-some-data
-
- # Caveat, this probably doesn't work on Windows because the code is dependent on the unix SELECT syscall.
- # Details: https://docs.python.org/2/library/select.html#select.select
- # This isn't a real problem now, because gitlint as a whole doesn't support Windows (see #20).
- # If we ever do, we probably want to fall back to the old detection mechanism of reading from the local git repo
- # in case there's no TTY connected to STDIN.
- return select.select([sys.stdin, ], [], [], 0.0)[0]
+def get_stdin_data():
+ """ Helper function that returns data send to stdin or False if nothing is send """
+ # STDIN can only be 3 different types of things ("modes")
+ # 1. An interactive terminal device (i.e. a TTY -> sys.stdin.isatty() or stat.S_ISCHR)
+ # 2. A (named) pipe (stat.S_ISFIFO)
+ # 3. A regular file (stat.S_ISREG)
+ # Technically, STDIN can also be other device type like a named unix socket (stat.S_ISSOCK), but we don't
+ # support that in gitlint (at least not today).
+ #
+ # Now, the behavior that we want is the following:
+ # If someone sends something directly to gitlint via a pipe or a regular file, read it. If not, read from the
+ # local repository.
+ # Note that we don't care about whether STDIN is a TTY or not, we only care whether data is via a pipe or regular
+ # file.
+ # However, in case STDIN is not a TTY, it HAS to be one of the 2 other things (pipe or regular file), even if
+ # no-one is actually sending anything to gitlint over them. In this case, we still want to read from the local
+ # repository.
+ # To support this use-case (which is common in CI runners such as Jenkins and Gitlab), we need to actually attempt
+ # to read from STDIN in case it's a pipe or regular file. In case that fails, then we'll fall back to reading
+ # from the local repo.
+
+ mode = os.fstat(sys.stdin.fileno()).st_mode
+ stdin_is_pipe_or_file = stat.S_ISFIFO(mode) or stat.S_ISREG(mode)
+ if stdin_is_pipe_or_file:
+ input_data = sys.stdin.read()
+ # Only return the input data if there's actually something passed
+ # i.e. don't consider empty piped data
+ if len(input_data) != 0:
+ return ustr(input_data)
+ return False
@click.group(invoke_without_command=True, epilog="When no COMMAND is specified, gitlint defaults to 'gitlint lint'.")
@@ -162,13 +180,20 @@ def lint(ctx):
lint_config = ctx.obj[0]
msg_filename = ctx.obj[3]
- # If we get data via stdin, then let's consider that our commit message, otherwise parse it from the local git repo.
+ # Let's determine where our input data is coming from:
+ # Order of precedence:
+ # 1. Any data specified via --msg-filename
+ # 2. Any data sent to stdin
+ # 3. Fallback to reading from local repository
+ stdin_input = get_stdin_data()
if msg_filename:
+ LOG.debug("Attempting to read from --msg-filename.")
gitcontext = GitContext.from_commit_msg(ustr(msg_filename.read()))
- elif stdin_has_data():
- stdin_str = ustr(sys.stdin.read())
- gitcontext = GitContext.from_commit_msg(stdin_str)
+ elif stdin_input:
+ LOG.debug("No --msg-filename flag. Attempting to read from stdin.")
+ gitcontext = GitContext.from_commit_msg(stdin_input)
else:
+ LOG.debug("No --msg-filename flag, no or empty data passed to stdin. Attempting to read from the local repo.")
gitcontext = GitContext.from_local_repository(lint_config.target, ctx.obj[2])
number_of_commits = len(gitcontext.commits)
diff --git a/gitlint/tests/config/test_config_precedence.py b/gitlint/tests/config/test_config_precedence.py
index 49a24ad..9689e55 100644
--- a/gitlint/tests/config/test_config_precedence.py
+++ b/gitlint/tests/config/test_config_precedence.py
@@ -25,7 +25,7 @@ class LintConfigPrecedenceTests(BaseTestCase):
def setUp(self):
self.cli = CliRunner()
- @patch('gitlint.cli.stdin_has_data', return_value=True)
+ @patch('gitlint.cli.get_stdin_data', return_value=u"WIP\n\nThis is å test message\n")
def test_config_precedence(self, _):
# TODO(jroovers): this test really only test verbosity, we need to do some refactoring to gitlint.cli
# to more easily test everything
@@ -34,40 +34,37 @@ class LintConfigPrecedenceTests(BaseTestCase):
# 2. commandline -c flags
# 3. config file
# 4. default config
- input_text = u"WIP\n\nThis is å test message\n"
config_path = self.get_sample_path("config/gitlintconfig")
# 1. commandline convenience flags
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path],
- input=input_text)
+ result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path])
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP\"\n")
# 2. commandline -c flags
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path], input=input_text)
+ result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path])
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive)\n")
# 3. config file
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- result = self.cli.invoke(cli.cli, ["--config", config_path], input=input_text)
+ result = self.cli.invoke(cli.cli, ["--config", config_path])
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5\n")
# 4. default config
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- result = self.cli.invoke(cli.cli, input=input_text)
+ result = self.cli.invoke(cli.cli)
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP\"\n")
- @patch('gitlint.cli.stdin_has_data', return_value=True)
- def test_ignore_precedence(self, _):
+ @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: This is å test")
+ def test_ignore_precedence(self, get_stdin_data):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
# --ignore takes precedence over -c general.ignore
- result = self.cli.invoke(cli.cli, ["-c", "general.ignore=T5", "--ignore", "B6"],
- input=u"WIP: This is å test")
+ result = self.cli.invoke(cli.cli, ["-c", "general.ignore=T5", "--ignore", "B6"])
self.assertEqual(result.output, "")
self.assertEqual(result.exit_code, 1)
# We still expect the T5 violation, but no B6 violation as --ignore overwrites -c general.ignore
@@ -76,10 +73,11 @@ class LintConfigPrecedenceTests(BaseTestCase):
# test that we can also still configure a rule that is first ignored but then not
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ get_stdin_data.return_value = u"This is å test"
# --ignore takes precedence over -c general.ignore
result = self.cli.invoke(cli.cli, ["-c", "general.ignore=title-max-length",
"-c", "title-max-length.line-length=5",
- "--ignore", "B6"], input=u"This is å test")
+ "--ignore", "B6"])
self.assertEqual(result.output, "")
self.assertEqual(result.exit_code, 1)
diff --git a/gitlint/tests/test_cli.py b/gitlint/tests/test_cli.py
index 2f2ffd6..0bef488 100644
--- a/gitlint/tests/test_cli.py
+++ b/gitlint/tests/test_cli.py
@@ -63,7 +63,7 @@ class CLITests(BaseTestCase):
result = self.cli.invoke(cli.cli, ["--version"])
self.assertEqual(result.output.split("\n")[0], "cli, version {0}".format(__version__))
- @patch('gitlint.cli.stdin_has_data', return_value=False)
+ @patch('gitlint.cli.get_stdin_data', return_value=False)
@patch('gitlint.git.sh')
def test_lint(self, sh, _):
""" Test for basic simple linting functionality """
@@ -77,7 +77,7 @@ class CLITests(BaseTestCase):
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.stdin_has_data', return_value=False)
+ @patch('gitlint.cli.get_stdin_data', return_value=False)
@patch('gitlint.git.sh')
def test_lint_multiple_commits(self, sh, _):
""" Test for --commits option """
@@ -107,7 +107,7 @@ class CLITests(BaseTestCase):
self.assertEqual(stderr.getvalue(), expected)
self.assertEqual(result.exit_code, 3)
- @patch('gitlint.cli.stdin_has_data', return_value=False)
+ @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 """
@@ -138,7 +138,7 @@ class CLITests(BaseTestCase):
self.assertEqual(stderr.getvalue(), expected)
self.assertEqual(result.exit_code, 3)
- @patch('gitlint.cli.stdin_has_data', return_value=False)
+ @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
@@ -175,7 +175,7 @@ class CLITests(BaseTestCase):
self.assertEqual(stderr.getvalue(), expected)
self.assertEqual(result.exit_code, 2)
- @patch('gitlint.cli.stdin_has_data', return_value=True)
+ @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 """
expected_output = u"1: T2 Title has trailing whitespace: \"WIP: tïtle \"\n" + \
@@ -183,12 +183,13 @@ class CLITests(BaseTestCase):
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')
+ result = self.cli.invoke(cli.cli)
self.assertEqual(stderr.getvalue(), expected_output)
self.assertEqual(result.exit_code, 3)
self.assertEqual(result.output, "")
- def test_from_filename(self):
+ @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:
@@ -197,28 +198,27 @@ class CLITests(BaseTestCase):
f.write("Commït title\n")
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- args = ["--msg-filename", msg_filename]
- result = self.cli.invoke(cli.cli, args)
+ 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.stdin_has_data', return_value=True)
+ @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"], input=u"WIP: tïtle \n")
+ result = self.cli.invoke(cli.cli, ["--silent"])
self.assertEqual(stderr.getvalue(), "")
self.assertEqual(result.exit_code, 3)
self.assertEqual(result.output, "")
- @patch('gitlint.cli.stdin_has_data', return_value=True)
+ @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"], input=u"WIP: tïtle \n")
+ 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, "")
@@ -241,7 +241,7 @@ class CLITests(BaseTestCase):
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.stdin_has_data', return_value=False)
+ @patch('gitlint.cli.get_stdin_data', return_value=False)
@patch('gitlint.git.sh')
def test_debug(self, sh, _):
""" Test for --debug option """
@@ -278,6 +278,8 @@ class CLITests(BaseTestCase):
u"DEBUG: gitlint.cli Gitlint version: {0}".format(__version__),
self.get_expected('debug_configuration_output1', {'config_path': config_path,
'target': os.path.abspath(os.getcwd())}),
+ u"DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. " +
+ u"Attempting to read from the local repo.",
u"DEBUG: gitlint.lint Linting commit 6f29bf81a8322a04071bb794666e48c443a90360",
u"DEBUG: gitlint.lint Commit Object\nAuthor: test åuthor1 <test-email1@föo.com>\n" +
u"Date: 2016-12-03 15:28:15+01:00\ncommït-title1\n\ncommït-body1",
@@ -291,13 +293,13 @@ class CLITests(BaseTestCase):
self.assert_logged(expected_logs)
- @patch('gitlint.cli.stdin_has_data', return_value=True)
+ @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"], input=u"Test tïtle\n")
+ 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)
@@ -312,12 +314,12 @@ class CLITests(BaseTestCase):
self.assertEqual(stderr.getvalue(), expected_output)
self.assertEqual(result.exit_code, 2)
- @patch('gitlint.cli.stdin_has_data', return_value=True)
+ @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("config/gitlintconfig")
- result = self.cli.invoke(cli.cli, ["--config", config_path], input=u"WIP: tëst")
+ 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)
@@ -345,7 +347,7 @@ class CLITests(BaseTestCase):
result = self.cli.invoke(cli.cli, ["--config", config_path])
self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)
- @patch('gitlint.cli.stdin_has_data', return_value=False)
+ @patch('gitlint.cli.get_stdin_data', return_value=False)
def test_target(self, _):
""" Test for the --target option """
os.environ["LANGUAGE"] = "C"
@@ -399,7 +401,7 @@ class CLITests(BaseTestCase):
"Error: File \"{0}\" already exists.\n".format(sample_path)
self.assertEqual(result.output, expected_msg)
- @patch('gitlint.cli.stdin_has_data', return_value=False)
+ @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 """
@@ -407,7 +409,7 @@ class CLITests(BaseTestCase):
result = self.cli.invoke(cli.cli)
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
- @patch('gitlint.cli.stdin_has_data', return_value=False)
+ @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. """
diff --git a/qa/base.py b/qa/base.py
index 74e3026..c36e69b 100644
--- a/qa/base.py
+++ b/qa/base.py
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
+# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable
import os
import sys
+import tempfile
from datetime import datetime
from uuid import uuid4
@@ -9,6 +11,19 @@ from unittest2 import TestCase
from sh import git, rm, touch, DEFAULT_ENCODING # pylint: disable=no-name-in-module
+def ustr(obj):
+ """ Python 2 and 3 utility method that converts an obj to unicode in python 2 and to a str object in python 3"""
+ if sys.version_info[0] == 2:
+ # If we are getting a string, then do an explicit decode
+ # else, just call the unicode method of the object
+ if type(obj) in [str, basestring]: # pragma: no cover # noqa # pylint: disable=unidiomatic-typecheck
+ return unicode(obj, DEFAULT_ENCODING) # pragma: no cover # noqa
+ else:
+ return unicode(obj) # pragma: no cover # noqa
+ else:
+ return str(obj)
+
+
class BaseTestCase(TestCase):
""" Base class of which all gitlint integration test classes are derived.
Provides a number of convenience methods. """
@@ -17,6 +32,13 @@ class BaseTestCase(TestCase):
maxDiff = None
tmp_git_repo = None
+ def setUp(self):
+ self.tmpfiles = []
+
+ def tearDown(self):
+ for tmpfile in self.tmpfiles:
+ os.remove(tmpfile)
+
@classmethod
def setUpClass(cls):
""" Sets up the integration tests by creating a new temporary git repository """
@@ -73,6 +95,15 @@ class BaseTestCase(TestCase):
_ok_code=ok_code, _env=environment)
return test_filename
+ def create_tmpfile(self, content):
+ """ Utility method to create temp files. These are cleaned at the end of the test """
+ # Not using a context manager to avoid unneccessary identation in test code
+ tmpfile, tmpfilepath = tempfile.mkstemp()
+ self.tmpfiles.append(tmpfilepath)
+ with os.fdopen(tmpfile, "w") as f:
+ f.write(content)
+ return tmpfilepath
+
@staticmethod
def get_example_path(filename=""):
examples_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../examples")
@@ -104,3 +135,11 @@ class BaseTestCase(TestCase):
if variable_dict:
expected = expected.format(**variable_dict)
return expected
+
+ @staticmethod
+ def mock_stdin():
+ """ Convenience method to create a Mock stdin object to deal with https://github.com/amoffat/sh/issues/427 """
+ class MockInput(object):
+ def read(self, _size): # pylint: disable=no-self-use
+ return
+ return MockInput()
diff --git a/qa/expected/debug_output1 b/qa/expected/debug_output1
index 93d7ff7..3ac7bff 100644
--- a/qa/expected/debug_output1
+++ b/qa/expected/debug_output1
@@ -44,6 +44,7 @@ target: {target}
M1: author-valid-email
regex=[^@ ]+@[^@ ]+\.[^@ ]+
+DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. Attempting to read from the local repo.
DEBUG: gitlint.lint Linting commit {commit_sha}
DEBUG: gitlint.lint Commit Object
Author: gitlint-test-user <gitlint@test.com>
diff --git a/qa/test_commits.py b/qa/test_commits.py
index ddff660..4620121 100644
--- a/qa/test_commits.py
+++ b/qa/test_commits.py
@@ -16,7 +16,7 @@ class CommitsTests(BaseTestCase):
self._create_simple_commit(u"Sïmple title2\n\nSimple bödy describing the commit2")
self._create_simple_commit(u"Sïmple title3\n\nSimple bödy describing the commit3")
output = gitlint("--commits", "test-branch-commits-base...test-branch-commits",
- _cwd=self.tmp_git_repo, _err_to_out=True)
+ _cwd=self.tmp_git_repo, _tty_in=True)
self.assertEqual(output, "")
def test_violations(self):
@@ -30,7 +30,7 @@ class CommitsTests(BaseTestCase):
self._create_simple_commit(u"Sïmple title3.\n")
commit_sha2 = self.get_last_commit_hash()[:10]
output = gitlint("--commits", "test-branch-commits-violations-base...test-branch-commits-violations",
- _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[4])
+ _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
expected = (u"Commit {0}:\n".format(commit_sha2) +
u"1: T3 Title has trailing punctuation (.): \"Sïmple title3.\"\n" +
u"3: B6 Body message is missing\n"
@@ -48,7 +48,7 @@ class CommitsTests(BaseTestCase):
commit_sha = self.get_last_commit_hash()
refspec = "{0}^...{0}".format(commit_sha)
self._create_simple_commit(u"Sïmple title3.\n")
- output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[2])
+ output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
expected = (u"1: T3 Title has trailing punctuation (.): \"Sïmple title2.\"\n" +
u"3: B6 Body message is missing\n")
self.assertEqual(output.exit_code, 2)
@@ -60,8 +60,8 @@ class CommitsTests(BaseTestCase):
self._create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
self._create_simple_commit(u"Sïmple title", git_repo=tmp_git_repo)
self._create_simple_commit(u"WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
- output = gitlint("--commits", "HEAD", _cwd=tmp_git_repo, _err_to_out=True, _ok_code=[3])
- revlist = git("rev-list", "HEAD", _err_to_out=True, _cwd=tmp_git_repo).split()
+ output = gitlint("--commits", "HEAD", _cwd=tmp_git_repo, _tty_in=True, _ok_code=[3])
+ revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
expected = (
u"Commit {0}:\n".format(revlist[0][:10]) +
@@ -86,11 +86,10 @@ class CommitsTests(BaseTestCase):
self._create_simple_commit(
u"Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo)
self._create_simple_commit(u"Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo)
- revlist = git("rev-list", "HEAD", _err_to_out=True, _cwd=tmp_git_repo).split()
+ revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
config_path = self.get_sample_path("config/ignore-release-commits")
- output = gitlint("--commits", "HEAD", "--config", config_path, _cwd=tmp_git_repo,
- _tty_in=False, _err_to_out=True, _ok_code=[4])
+ output = gitlint("--commits", "HEAD", "--config", config_path, _cwd=tmp_git_repo, _tty_in=True, _ok_code=[4])
expected = (
u"Commit {0}:\n".format(revlist[0][:10]) +
diff --git a/qa/test_config.py b/qa/test_config.py
index 3120934..b7cb733 100644
--- a/qa/test_config.py
+++ b/qa/test_config.py
@@ -14,30 +14,30 @@ class ConfigTests(BaseTestCase):
def test_ignore_by_id(self):
self._create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
- output = gitlint("--ignore", "T5,B4", _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[1])
+ output = gitlint("--ignore", "T5,B4", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[1])
expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
self.assertEqual(output, expected)
def test_ignore_by_name(self):
self._create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
output = gitlint("--ignore", "title-must-not-contain-word,body-first-line-empty",
- _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[1])
+ _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
self.assertEqual(output, expected)
def test_verbosity(self):
self._create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
- output = gitlint("-v", _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[3])
+ output = gitlint("-v", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
expected = u"1: T3\n1: T5\n2: B4\n"
self.assertEqual(output, expected)
- output = gitlint("-vv", _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[3])
+ output = gitlint("-vv", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
expected = u"1: T3 Title has trailing punctuation (.)\n" + \
u"1: T5 Title contains the word 'WIP' (case-insensitive)\n" + \
u"2: B4 Second line is not empty\n"
self.assertEqual(output, expected)
- output = gitlint("-vvv", _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[3])
+ output = gitlint("-vvv", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n" + \
u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: Thïs is a title.\"\n" + \
u"2: B4 Second line is not empty: \"Contënt on the second line\"\n"
@@ -49,7 +49,7 @@ class ConfigTests(BaseTestCase):
def test_set_rule_option(self):
self._create_simple_commit(u"This ïs a title.")
- output = gitlint("-c", "title-max-length.line-length=5", _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[3])
+ output = gitlint("-c", "title-max-length.line-length=5", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[3])
expected = u"1: T1 Title exceeds max length (16>5): \"This ïs a title.\"\n" + \
u"1: T3 Title has trailing punctuation (.): \"This ïs a title.\"\n" + \
"3: B6 Body message is missing\n"
@@ -60,7 +60,7 @@ class ConfigTests(BaseTestCase):
"This line of the body is here because we need it"
self._create_simple_commit(commit_msg)
config_path = self.get_sample_path("config/gitlintconfig")
- output = gitlint("--config", config_path, _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[5])
+ output = gitlint("--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
expected = "1: T1 Title exceeds max length (42>20)\n" + \
"1: T5 Title contains the word 'WIP' (case-insensitive)\n" + \
@@ -76,7 +76,7 @@ class ConfigTests(BaseTestCase):
self._create_simple_commit(commit_msg)
commit_sha = self.get_last_commit_hash()
config_path = self.get_sample_path("config/gitlintconfig")
- output = gitlint("--config", config_path, "--debug", _cwd=self.tmp_git_repo, _err_to_out=True, _ok_code=[5])
+ output = gitlint("--config", config_path, "--debug", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
expected_date = git("log", "-1", "--pretty=%ai", _cwd=self.tmp_git_repo)
expected_date = arrow.get(str(expected_date), "YYYY-MM-DD HH:mm:ss Z").datetime
diff --git a/qa/test_gitlint.py b/qa/test_gitlint.py
index d6a80ae..f61b281 100644
--- a/qa/test_gitlint.py
+++ b/qa/test_gitlint.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
-from datetime import datetime
import os
-from sh import git, gitlint, echo # pylint: disable=no-name-in-module
+from sh import git, gitlint # pylint: disable=no-name-in-module
from qa.base import BaseTestCase
@@ -10,10 +9,9 @@ class IntegrationTests(BaseTestCase):
def test_successful(self):
# Test for STDIN with and without a TTY attached
- for has_tty in [True, False]:
- self._create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit")
- output = gitlint(_cwd=self.tmp_git_repo, _tty_in=has_tty, _err_to_out=True)
- self.assertEqual(output, "")
+ self._create_simple_commit(u"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.assertEqual(output, "")
def test_successful_merge_commit(self):
# Create branch on master
@@ -105,40 +103,38 @@ class IntegrationTests(BaseTestCase):
self.assertEqual(output, expected)
def test_violations(self):
- # Test for STDIN with and without a TTY attached
- for has_tty in [True, False]:
- commit_msg = u"WIP: This ïs a title.\nContent on the sëcond line"
- self._create_simple_commit(commit_msg)
- # 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
- output = gitlint(_cwd=self.tmp_git_repo, _tty_in=has_tty, _err_to_out=True, _ok_code=[3])
-
- expected = u"1: T3 Title has trailing punctuation (.): \"WIP: This ïs a title.\"\n" + \
- u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This ïs a title.\"\n" + \
- u"2: B4 Second line is not empty: \"Content on the sëcond line\"\n"
- self.assertEqual(output, expected)
-
- def test_pipe_input(self):
- # NOTE: There is no use in testing this with _tty_in=True, because if you pipe something into a command
- # there never is a TTY connected to stdin (per definition).
- output = gitlint(echo(u"WIP: Pïpe test."), _tty_in=False, _err_to_out=True, _ok_code=[3])
-
- expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Pïpe test.\"\n" + \
- u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: Pïpe test.\"\n" + \
- u"3: B6 Body message is missing\n"
+ commit_msg = u"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])
+ expected = u"1: T3 Title has trailing punctuation (.): \"WIP: This ïs a title.\"\n" + \
+ u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This ïs a title.\"\n" + \
+ u"2: B4 Second line is not empty: \"Content on the sëcond line\"\n"
self.assertEqual(output, expected)
- def test_from_file(self):
- tmp_commit_msg_file = os.path.realpath("/tmp/commitmsg-%s" % datetime.now().strftime("%Y%m%d-%H%M%S"))
- with open(tmp_commit_msg_file, "w") as f:
- f.write("WIP: msg-fïlename test.")
+ 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=False, _err_to_out=True, _ok_code=[3])
+ output = gitlint("--msg-filename", tmp_commit_msg_file, _tty_in=True, _ok_code=[3])
expected = u"1: T3 Title has trailing punctuation (.): \"WIP: msg-fïlename test.\"\n" + \
- u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: msg-fïlename test.\"\n" + \
+ u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: msg-fïlename test.\"\n" + \
+ u"3: B6 Body message is missing\n"
+
+ self.assertEqual(output, expected)
+
+ 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
+ output = gitlint("--msg-filename", tmp_commit_msg_file, _in=self.mock_stdin(),
+ _tty_in=False, _err_to_out=True, _ok_code=[3])
+
+ expected = u"1: T3 Title has trailing punctuation (.): \"WIP: msg-fïlename NO TTY test.\"\n" + \
+ u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: msg-fïlename NO TTY test.\"\n" + \
u"3: B6 Body message is missing\n"
self.assertEqual(output, expected)
diff --git a/qa/test_stdin.py b/qa/test_stdin.py
new file mode 100644
index 0000000..317b53c
--- /dev/null
+++ b/qa/test_stdin.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+import subprocess
+from sh import gitlint, echo # pylint: disable=no-name-in-module
+from qa.base import BaseTestCase, ustr
+
+
+class StdInTests(BaseTestCase):
+ """ Integration tests for various STDIN scenarios for gitlint """
+
+ def test_stdin_pipe(self):
+ """ Test piping input into gitlint.
+ This is the equivalent of doing:
+