Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pre_commit/languages/all.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import unicode_literals

from pre_commit.languages import node
from pre_commit.languages import pcre
from pre_commit.languages import python
from pre_commit.languages import ruby
from pre_commit.languages import script
Expand Down Expand Up @@ -36,6 +37,7 @@

languages = {
'node': node,
'pcre': pcre,
'python': python,
'ruby': ruby,
'script': script,
Expand Down
6 changes: 5 additions & 1 deletion pre_commit/languages/helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from __future__ import unicode_literals


def file_args_to_stdin(file_args):
return '\n'.join(list(file_args) + [''])


def run_hook(env, hook, file_args):
return env.run(
' '.join(['xargs', hook['entry']] + hook['args']),
stdin='\n'.join(list(file_args) + ['']),
stdin=file_args_to_stdin(file_args),
retcode=None,
)

Expand Down
27 changes: 27 additions & 0 deletions pre_commit/languages/pcre.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import unicode_literals

from pre_commit.languages.helpers import file_args_to_stdin
from pre_commit.util import shell_escape


ENVIRONMENT_DIR = None


def install_environment(repo_cmd_runner, version='default'):
"""Installation for pcre type is a noop."""
raise AssertionError('Cannot install pcre repo.')


def run_hook(repo_cmd_runner, hook, file_args):
# For PCRE the entry is the regular expression to match
return repo_cmd_runner.run(
[
'xargs', 'sh', '-c',
# Grep usually returns 0 for matches, and nonzero for non-matches
# so we flip it here.
'! grep -H -n -P {0} $@'.format(shell_escape(hook['entry'])),
'--',
],
stdin=file_args_to_stdin(file_args),
retcode=None,
)
6 changes: 5 additions & 1 deletion pre_commit/languages/script.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from __future__ import unicode_literals

from pre_commit.languages.helpers import file_args_to_stdin


ENVIRONMENT_DIR = None


def install_environment(repo_cmd_runner, version='default'):
"""Installation for script type is a noop."""
raise AssertionError('Cannot install script repo.')


def run_hook(repo_cmd_runner, hook, file_args):
return repo_cmd_runner.run(
['xargs', '{{prefix}}{0}'.format(hook['entry'])] + hook['args'],
# TODO: this is duplicated in pre_commit/languages/helpers.py
stdin='\n'.join(list(file_args) + ['']),
stdin=file_args_to_stdin(file_args),
retcode=None,
)
6 changes: 4 additions & 2 deletions pre_commit/languages/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

import shlex

from pre_commit.languages.helpers import file_args_to_stdin


ENVIRONMENT_DIR = None


def install_environment(repo_cmd_runner, version='default'):
"""Installation for system type is a noop."""
raise AssertionError('Cannot install system repo.')


def run_hook(repo_cmd_runner, hook, file_args):
return repo_cmd_runner.run(
['xargs'] + shlex.split(hook['entry']) + hook['args'],
# TODO: this is duplicated in pre_commit/languages/helpers.py
stdin='\n'.join(list(file_args) + ['']),
stdin=file_args_to_stdin(file_args),
retcode=None,
)
5 changes: 4 additions & 1 deletion pre_commit/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ def clean_path_on_failure(path):
raise


# TODO: asottile.contextlib this with a forward port of nested
@contextlib.contextmanager
def noop_context():
yield


def shell_escape(arg):
return "'" + arg.replace("'", "'\"'\"'".strip()) + "'"
10 changes: 10 additions & 0 deletions testing/resources/pcre_hooks_repo/hooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- id: regex-with-quotes
name: Regex with quotes
entry: "foo'bar"
language: pcre
files: ''
- id: other-regex
name: Other regex
entry: ^\[INFO\]
language: pcre
files: ''
27 changes: 24 additions & 3 deletions tests/languages/all_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
from __future__ import unicode_literals

import inspect
import pytest

from pre_commit.languages.all import all_languages
from pre_commit.languages.all import languages


@pytest.mark.parametrize('language', all_languages)
def test_all_languages_support_interface(language):
assert hasattr(languages[language], 'install_environment')
assert hasattr(languages[language], 'run_hook')
def test_install_environment_argspec(language):
expected_argspec = inspect.ArgSpec(
args=['repo_cmd_runner', 'version'],
varargs=None,
keywords=None,
defaults=('default',),
)
argspec = inspect.getargspec(languages[language].install_environment)
assert argspec == expected_argspec


@pytest.mark.parametrize('language', all_languages)
def test_ENVIRONMENT_DIR(language):
assert hasattr(languages[language], 'ENVIRONMENT_DIR')


@pytest.mark.parametrize('language', all_languages)
def test_run_hook_argpsec(language):
expected_argspec = inspect.ArgSpec(
args=['repo_cmd_runner', 'hook', 'file_args'],
varargs=None, keywords=None, defaults=None,
)
argspec = inspect.getargspec(languages[language].run_hook)
assert argspec == expected_argspec
16 changes: 16 additions & 0 deletions tests/languages/helpers_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import absolute_import
from __future__ import unicode_literals

from pre_commit.languages.helpers import file_args_to_stdin


def test_file_args_to_stdin_empty():
assert file_args_to_stdin([]) == ''


def test_file_args_to_stdin_some():
assert file_args_to_stdin(['foo', 'bar']) == 'foo\nbar\n'


def test_file_args_to_stdin_tuple():
assert file_args_to_stdin(('foo', 'bar')) == 'foo\nbar\n'
76 changes: 74 additions & 2 deletions tests/repository_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from __future__ import unicode_literals

import io
import mock
import os.path
import pytest
Expand All @@ -25,12 +26,20 @@ def test_install_python_repo_in_env(tmpdir_factory, store):
assert os.path.exists(os.path.join(store.directory, repo.sha, 'py_env'))


def _test_hook_repo(tmpdir_factory, store, repo_path, hook_id, args, expected):
def _test_hook_repo(
tmpdir_factory,
store,
repo_path,
hook_id,
args,
expected,
expected_return_code=0,
):
path = make_repo(tmpdir_factory, repo_path)
config = make_config_from_repo(path)
repo = Repository.create(config, store)
ret = repo.run_hook(hook_id, args)
assert ret[0] == 0
assert ret[0] == expected_return_code
assert ret[1] == expected


Expand Down Expand Up @@ -102,6 +111,69 @@ def test_run_a_script_hook(tmpdir_factory, store):
)


@pytest.mark.integration
def test_pcre_hook_no_match(tmpdir_factory, store):
path = git_dir(tmpdir_factory)
with local.cwd(path):
with io.open('herp', 'w') as herp:
herp.write('foo')

with io.open('derp', 'w') as derp:
derp.write('bar')

_test_hook_repo(
tmpdir_factory, store, 'pcre_hooks_repo',
'regex-with-quotes', ['herp', 'derp'], '',
)

_test_hook_repo(
tmpdir_factory, store, 'pcre_hooks_repo',
'other-regex', ['herp', 'derp'], '',
)


@pytest.mark.integration
def test_pcre_hook_matching(tmpdir_factory, store):
path = git_dir(tmpdir_factory)
with local.cwd(path):
with io.open('herp', 'w') as herp:
herp.write("\nherpfoo'bard\n")

with io.open('derp', 'w') as derp:
derp.write('[INFO] information yo\n')

_test_hook_repo(
tmpdir_factory, store, 'pcre_hooks_repo',
'regex-with-quotes', ['herp', 'derp'], "herp:2:herpfoo'bard\n",
expected_return_code=123,
)

_test_hook_repo(
tmpdir_factory, store, 'pcre_hooks_repo',
'other-regex', ['herp', 'derp'], 'derp:1:[INFO] information yo\n',
expected_return_code=123,
)


@pytest.mark.integration
def test_pcre_many_files(tmpdir_factory, store):
# This is intended to simulate lots of passing files and one failing file
# to make sure it still fails. This is not the case when naively using
# a system hook with `grep -H -n '...'` and expected_return_code=123.
path = git_dir(tmpdir_factory)
with local.cwd(path):
with io.open('herp', 'w') as herp:
herp.write('[INFO] info\n')

_test_hook_repo(
tmpdir_factory, store, 'pcre_hooks_repo',
'other-regex',
['/dev/null'] * 15000 + ['herp'],
'herp:1:[INFO] info\n',
expected_return_code=123,
)


@pytest.mark.integration
def test_cwd_of_hook(tmpdir_factory, store):
# Note: this doubles as a test for `system` hooks
Expand Down
13 changes: 13 additions & 0 deletions tests/util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pre_commit.util import clean_path_on_failure
from pre_commit.util import entry
from pre_commit.util import memoize_by_cwd
from pre_commit.util import shell_escape


@pytest.fixture
Expand Down Expand Up @@ -99,3 +100,15 @@ class MySystemExit(SystemExit):
raise MySystemExit

assert not os.path.exists('foo')


@pytest.mark.parametrize(
('input_str', 'expected'),
(
('', "''"),
('foo"bar', "'foo\"bar'"),
("foo'bar", "'foo'\"'\"'bar'")
),
)
def test_shell_escape(input_str, expected):
assert shell_escape(input_str) == expected