Adding upstream version 4.5.0+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
27cd5628db
commit
6bd375ed5f
108 changed files with 6514 additions and 0 deletions
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
134
tests/check_added_large_files_test.py
Normal file
134
tests/check_added_large_files_test.py
Normal file
|
@ -0,0 +1,134 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_added_large_files import find_large_added_files
|
||||
from pre_commit_hooks.check_added_large_files import main
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
def test_nothing_added(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
assert find_large_added_files(['f.py'], 0) == 0
|
||||
|
||||
|
||||
def test_adding_something(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
|
||||
# Should fail with max size of 0
|
||||
assert find_large_added_files(['f.py'], 0) == 1
|
||||
|
||||
|
||||
def test_add_something_giant(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write('a' * 10000)
|
||||
|
||||
# Should not fail when not added
|
||||
assert find_large_added_files(['f.py'], 0) == 0
|
||||
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
|
||||
# Should fail with strict bound
|
||||
assert find_large_added_files(['f.py'], 0) == 1
|
||||
|
||||
# Should also fail with actual bound
|
||||
assert find_large_added_files(['f.py'], 9) == 1
|
||||
|
||||
# Should pass with higher bound
|
||||
assert find_large_added_files(['f.py'], 10) == 0
|
||||
|
||||
|
||||
def test_enforce_all(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write('a' * 10000)
|
||||
|
||||
# Should fail, when not staged with enforce_all
|
||||
assert find_large_added_files(['f.py'], 0, enforce_all=True) == 1
|
||||
|
||||
# Should pass, when not staged without enforce_all
|
||||
assert find_large_added_files(['f.py'], 0, enforce_all=False) == 0
|
||||
|
||||
|
||||
def test_added_file_not_in_pre_commits_list(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
|
||||
# Should pass even with a size of 0
|
||||
assert find_large_added_files(['g.py'], 0) == 0
|
||||
|
||||
|
||||
def test_integration(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
assert main(argv=[]) == 0
|
||||
|
||||
temp_git_dir.join('f.py').write('a' * 10000)
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
|
||||
# Should not fail with default
|
||||
assert main(argv=['f.py']) == 0
|
||||
|
||||
# Should fail with --maxkb
|
||||
assert main(argv=['--maxkb', '9', 'f.py']) == 1
|
||||
|
||||
|
||||
def has_gitlfs():
|
||||
return shutil.which('git-lfs') is not None
|
||||
|
||||
|
||||
xfailif_no_gitlfs = pytest.mark.xfail(
|
||||
not has_gitlfs(), reason='This test requires git-lfs',
|
||||
)
|
||||
|
||||
|
||||
@xfailif_no_gitlfs
|
||||
def test_allows_gitlfs(temp_git_dir): # pragma: no cover
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'lfs', 'install', '--local')
|
||||
temp_git_dir.join('f.py').write('a' * 10000)
|
||||
cmd_output('git', 'lfs', 'track', 'f.py')
|
||||
cmd_output('git', 'add', '--', '.')
|
||||
# Should succeed
|
||||
assert main(('--maxkb', '9', 'f.py')) == 0
|
||||
|
||||
|
||||
@xfailif_no_gitlfs
|
||||
def test_moves_with_gitlfs(temp_git_dir): # pragma: no cover
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'lfs', 'install', '--local')
|
||||
cmd_output('git', 'lfs', 'track', 'a.bin', 'b.bin')
|
||||
# First add the file we're going to move
|
||||
temp_git_dir.join('a.bin').write('a' * 10000)
|
||||
cmd_output('git', 'add', '--', '.')
|
||||
git_commit('-am', 'foo')
|
||||
# Now move it and make sure the hook still succeeds
|
||||
cmd_output('git', 'mv', 'a.bin', 'b.bin')
|
||||
assert main(('--maxkb', '9', 'b.bin')) == 0
|
||||
|
||||
|
||||
@xfailif_no_gitlfs
|
||||
def test_enforce_allows_gitlfs(temp_git_dir): # pragma: no cover
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'lfs', 'install', '--local')
|
||||
temp_git_dir.join('f.py').write('a' * 10000)
|
||||
cmd_output('git', 'lfs', 'track', 'f.py')
|
||||
cmd_output('git', 'add', '--', '.')
|
||||
# With --enforce-all large files on git lfs should succeed
|
||||
assert main(('--enforce-all', '--maxkb', '9', 'f.py')) == 0
|
||||
|
||||
|
||||
@xfailif_no_gitlfs
|
||||
def test_enforce_allows_gitlfs_after_commit(temp_git_dir): # pragma: no cover
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'lfs', 'install', '--local')
|
||||
temp_git_dir.join('f.py').write('a' * 10000)
|
||||
cmd_output('git', 'lfs', 'track', 'f.py')
|
||||
cmd_output('git', 'add', '--', '.')
|
||||
git_commit('-am', 'foo')
|
||||
# With --enforce-all large files on git lfs should succeed
|
||||
assert main(('--enforce-all', '--maxkb', '9', 'f.py')) == 0
|
14
tests/check_ast_test.py
Normal file
14
tests/check_ast_test.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_ast import main
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
||||
def test_failing_file():
|
||||
ret = main([get_resource_path('cannot_parse_ast.notpy')])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_passing_file():
|
||||
ret = main([__file__])
|
||||
assert ret == 0
|
151
tests/check_builtin_literals_test.py
Normal file
151
tests/check_builtin_literals_test.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_builtin_literals import Call
|
||||
from pre_commit_hooks.check_builtin_literals import main
|
||||
from pre_commit_hooks.check_builtin_literals import Visitor
|
||||
|
||||
BUILTIN_CONSTRUCTORS = '''\
|
||||
import builtins
|
||||
|
||||
c1 = complex()
|
||||
d1 = dict()
|
||||
f1 = float()
|
||||
i1 = int()
|
||||
l1 = list()
|
||||
s1 = str()
|
||||
t1 = tuple()
|
||||
|
||||
c2 = builtins.complex()
|
||||
d2 = builtins.dict()
|
||||
f2 = builtins.float()
|
||||
i2 = builtins.int()
|
||||
l2 = builtins.list()
|
||||
s2 = builtins.str()
|
||||
t2 = builtins.tuple()
|
||||
'''
|
||||
BUILTIN_LITERALS = '''\
|
||||
c1 = 0j
|
||||
d1 = {}
|
||||
f1 = 0.0
|
||||
i1 = 0
|
||||
l1 = []
|
||||
s1 = ''
|
||||
t1 = ()
|
||||
'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def visitor():
|
||||
return Visitor()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('expression', 'calls'),
|
||||
[
|
||||
# see #285
|
||||
('x[0]()', []),
|
||||
# complex
|
||||
('0j', []),
|
||||
('complex()', [Call('complex', 1, 0)]),
|
||||
('complex(0, 0)', []),
|
||||
("complex('0+0j')", []),
|
||||
('builtins.complex()', []),
|
||||
# float
|
||||
('0.0', []),
|
||||
('float()', [Call('float', 1, 0)]),
|
||||
("float('0.0')", []),
|
||||
('builtins.float()', []),
|
||||
# int
|
||||
('0', []),
|
||||
('int()', [Call('int', 1, 0)]),
|
||||
("int('0')", []),
|
||||
('builtins.int()', []),
|
||||
# list
|
||||
('[]', []),
|
||||
('list()', [Call('list', 1, 0)]),
|
||||
("list('abc')", []),
|
||||
("list([c for c in 'abc'])", []),
|
||||
("list(c for c in 'abc')", []),
|
||||
('builtins.list()', []),
|
||||
# str
|
||||
("''", []),
|
||||
('str()', [Call('str', 1, 0)]),
|
||||
("str('0')", []),
|
||||
('builtins.str()', []),
|
||||
# tuple
|
||||
('()', []),
|
||||
('tuple()', [Call('tuple', 1, 0)]),
|
||||
("tuple('abc')", []),
|
||||
("tuple([c for c in 'abc'])", []),
|
||||
("tuple(c for c in 'abc')", []),
|
||||
('builtins.tuple()', []),
|
||||
],
|
||||
)
|
||||
def test_non_dict_exprs(visitor, expression, calls):
|
||||
visitor.visit(ast.parse(expression))
|
||||
assert visitor.builtin_type_calls == calls
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('expression', 'calls'),
|
||||
[
|
||||
('{}', []),
|
||||
('dict()', [Call('dict', 1, 0)]),
|
||||
('dict(a=1, b=2, c=3)', []),
|
||||
("dict(**{'a': 1, 'b': 2, 'c': 3})", []),
|
||||
("dict([(k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)]])", []),
|
||||
("dict((k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)])", []),
|
||||
('builtins.dict()', []),
|
||||
],
|
||||
)
|
||||
def test_dict_allow_kwargs_exprs(visitor, expression, calls):
|
||||
visitor.visit(ast.parse(expression))
|
||||
assert visitor.builtin_type_calls == calls
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('expression', 'calls'),
|
||||
[
|
||||
('dict()', [Call('dict', 1, 0)]),
|
||||
('dict(a=1, b=2, c=3)', [Call('dict', 1, 0)]),
|
||||
("dict(**{'a': 1, 'b': 2, 'c': 3})", [Call('dict', 1, 0)]),
|
||||
('builtins.dict()', []),
|
||||
],
|
||||
)
|
||||
def test_dict_no_allow_kwargs_exprs(expression, calls):
|
||||
visitor = Visitor(allow_dict_kwargs=False)
|
||||
visitor.visit(ast.parse(expression))
|
||||
assert visitor.builtin_type_calls == calls
|
||||
|
||||
|
||||
def test_ignore_constructors():
|
||||
visitor = Visitor(
|
||||
ignore=('complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'),
|
||||
)
|
||||
visitor.visit(ast.parse(BUILTIN_CONSTRUCTORS))
|
||||
assert visitor.builtin_type_calls == []
|
||||
|
||||
|
||||
def test_failing_file(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write(BUILTIN_CONSTRUCTORS)
|
||||
rc = main([str(f)])
|
||||
assert rc == 1
|
||||
|
||||
|
||||
def test_passing_file(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write(BUILTIN_LITERALS)
|
||||
rc = main([str(f)])
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_failing_file_ignore_all(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write(BUILTIN_CONSTRUCTORS)
|
||||
rc = main(['--ignore=complex,dict,float,int,list,str,tuple', str(f)])
|
||||
assert rc == 0
|
15
tests/check_byte_order_marker_test.py
Normal file
15
tests/check_byte_order_marker_test.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks import check_byte_order_marker
|
||||
|
||||
|
||||
def test_failure(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_text('ohai', encoding='utf-8-sig')
|
||||
assert check_byte_order_marker.main((str(f),)) == 1
|
||||
|
||||
|
||||
def test_success(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_text('ohai', encoding='utf-8')
|
||||
assert check_byte_order_marker.main((str(f),)) == 0
|
124
tests/check_case_conflict_test.py
Normal file
124
tests/check_case_conflict_test.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_case_conflict import find_conflicting_filenames
|
||||
from pre_commit_hooks.check_case_conflict import main
|
||||
from pre_commit_hooks.check_case_conflict import parents
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from testing.util import git_commit
|
||||
|
||||
skip_win32 = pytest.mark.skipif(
|
||||
sys.platform == 'win32',
|
||||
reason='case conflicts between directories and files',
|
||||
)
|
||||
|
||||
|
||||
def test_parents():
|
||||
assert set(parents('a')) == set()
|
||||
assert set(parents('a/b')) == {'a'}
|
||||
assert set(parents('a/b/c')) == {'a/b', 'a'}
|
||||
assert set(parents('a/b/c/d')) == {'a/b/c', 'a/b', 'a'}
|
||||
|
||||
|
||||
def test_nothing_added(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
assert find_conflicting_filenames(['f.py']) == 0
|
||||
|
||||
|
||||
def test_adding_something(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
|
||||
assert find_conflicting_filenames(['f.py']) == 0
|
||||
|
||||
|
||||
def test_adding_something_with_conflict(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
temp_git_dir.join('F.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'F.py')
|
||||
|
||||
assert find_conflicting_filenames(['f.py', 'F.py']) == 1
|
||||
|
||||
|
||||
@skip_win32 # pragma: win32 no cover
|
||||
def test_adding_files_with_conflicting_directories(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.mkdir('dir').join('x').write('foo')
|
||||
temp_git_dir.mkdir('DIR').join('y').write('foo')
|
||||
cmd_output('git', 'add', '-A')
|
||||
|
||||
assert find_conflicting_filenames([]) == 1
|
||||
|
||||
|
||||
@skip_win32 # pragma: win32 no cover
|
||||
def test_adding_files_with_conflicting_deep_directories(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.mkdir('x').mkdir('y').join('z').write('foo')
|
||||
temp_git_dir.join('X').write('foo')
|
||||
cmd_output('git', 'add', '-A')
|
||||
|
||||
assert find_conflicting_filenames([]) == 1
|
||||
|
||||
|
||||
@skip_win32 # pragma: win32 no cover
|
||||
def test_adding_file_with_conflicting_directory(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.mkdir('dir').join('x').write('foo')
|
||||
temp_git_dir.join('DIR').write('foo')
|
||||
cmd_output('git', 'add', '-A')
|
||||
|
||||
assert find_conflicting_filenames([]) == 1
|
||||
|
||||
|
||||
def test_added_file_not_in_pre_commits_list(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
|
||||
assert find_conflicting_filenames(['g.py']) == 0
|
||||
|
||||
|
||||
def test_file_conflicts_with_committed_file(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
git_commit('-m', 'Add f.py')
|
||||
|
||||
temp_git_dir.join('F.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'F.py')
|
||||
|
||||
assert find_conflicting_filenames(['F.py']) == 1
|
||||
|
||||
|
||||
@skip_win32 # pragma: win32 no cover
|
||||
def test_file_conflicts_with_committed_dir(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
temp_git_dir.mkdir('dir').join('x').write('foo')
|
||||
cmd_output('git', 'add', '-A')
|
||||
git_commit('-m', 'Add f.py')
|
||||
|
||||
temp_git_dir.join('DIR').write('foo')
|
||||
cmd_output('git', 'add', '-A')
|
||||
|
||||
assert find_conflicting_filenames([]) == 1
|
||||
|
||||
|
||||
def test_integration(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
assert main(argv=[]) == 0
|
||||
|
||||
temp_git_dir.join('f.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'f.py')
|
||||
|
||||
assert main(argv=['f.py']) == 0
|
||||
|
||||
temp_git_dir.join('F.py').write("print('hello world')")
|
||||
cmd_output('git', 'add', 'F.py')
|
||||
|
||||
assert main(argv=['F.py']) == 1
|
69
tests/check_docstring_first_test.py
Normal file
69
tests/check_docstring_first_test.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_docstring_first import check_docstring_first
|
||||
from pre_commit_hooks.check_docstring_first import main
|
||||
|
||||
|
||||
# Contents, expected, expected_output
|
||||
TESTS = (
|
||||
# trivial
|
||||
(b'', 0, ''),
|
||||
# Acceptable
|
||||
(b'"foo"', 0, ''),
|
||||
# Docstring after code
|
||||
(
|
||||
b'from __future__ import unicode_literals\n'
|
||||
b'"foo"\n',
|
||||
1,
|
||||
'{filename}:2: Module docstring appears after code '
|
||||
'(code seen on line 1).\n',
|
||||
),
|
||||
# Test double docstring
|
||||
(
|
||||
b'"The real docstring"\n'
|
||||
b'from __future__ import absolute_import\n'
|
||||
b'"fake docstring"\n',
|
||||
1,
|
||||
'{filename}:3: Multiple module docstrings '
|
||||
'(first docstring on line 1).\n',
|
||||
),
|
||||
# Test multiple lines of code above
|
||||
(
|
||||
b'import os\n'
|
||||
b'import sys\n'
|
||||
b'"docstring"\n',
|
||||
1,
|
||||
'{filename}:3: Module docstring appears after code '
|
||||
'(code seen on line 1).\n',
|
||||
),
|
||||
# String literals in expressions are ok.
|
||||
(b'x = "foo"\n', 0, ''),
|
||||
)
|
||||
|
||||
|
||||
all_tests = pytest.mark.parametrize(
|
||||
('contents', 'expected', 'expected_out'), TESTS,
|
||||
)
|
||||
|
||||
|
||||
@all_tests
|
||||
def test_unit(capsys, contents, expected, expected_out):
|
||||
assert check_docstring_first(contents) == expected
|
||||
assert capsys.readouterr()[0] == expected_out.format(filename='<unknown>')
|
||||
|
||||
|
||||
@all_tests
|
||||
def test_integration(tmpdir, capsys, contents, expected, expected_out):
|
||||
f = tmpdir.join('test.py')
|
||||
f.write_binary(contents)
|
||||
assert main([str(f)]) == expected
|
||||
assert capsys.readouterr()[0] == expected_out.format(filename=str(f))
|
||||
|
||||
|
||||
def test_arbitrary_encoding(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
contents = '# -*- coding: cp1252\nx = "£"'.encode('cp1252')
|
||||
f.write_binary(contents)
|
||||
assert main([str(f)]) == 0
|
127
tests/check_executables_have_shebangs_test.py
Normal file
127
tests/check_executables_have_shebangs_test.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks import check_executables_have_shebangs
|
||||
from pre_commit_hooks.check_executables_have_shebangs import main
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
|
||||
skip_win32 = pytest.mark.skipif(
|
||||
sys.platform == 'win32',
|
||||
reason="non-git checks aren't relevant on windows",
|
||||
)
|
||||
|
||||
|
||||
@skip_win32 # pragma: win32 no cover
|
||||
@pytest.mark.parametrize(
|
||||
'content', (
|
||||
b'#!/bin/bash\nhello world\n',
|
||||
b'#!/usr/bin/env python3.6',
|
||||
b'#!python',
|
||||
'#!☃'.encode(),
|
||||
),
|
||||
)
|
||||
def test_has_shebang(content, tmpdir):
|
||||
path = tmpdir.join('path')
|
||||
path.write(content, 'wb')
|
||||
assert main((str(path),)) == 0
|
||||
|
||||
|
||||
@skip_win32 # pragma: win32 no cover
|
||||
@pytest.mark.parametrize(
|
||||
'content', (
|
||||
b'',
|
||||
b' #!python\n',
|
||||
b'\n#!python\n',
|
||||
b'python\n',
|
||||
'☃'.encode(),
|
||||
),
|
||||
)
|
||||
def test_bad_shebang(content, tmpdir, capsys):
|
||||
path = tmpdir.join('path')
|
||||
path.write(content, 'wb')
|
||||
assert main((str(path),)) == 1
|
||||
_, stderr = capsys.readouterr()
|
||||
assert stderr.startswith(f'{path}: marked executable but')
|
||||
|
||||
|
||||
def test_check_git_filemode_passing(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
cmd_output('git', 'init', '.')
|
||||
|
||||
f = tmpdir.join('f')
|
||||
f.write('#!/usr/bin/env bash')
|
||||
f_path = str(f)
|
||||
cmd_output('chmod', '+x', f_path)
|
||||
cmd_output('git', 'add', f_path)
|
||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
||||
|
||||
g = tmpdir.join('g').ensure()
|
||||
g_path = str(g)
|
||||
cmd_output('git', 'add', g_path)
|
||||
|
||||
# this is potentially a problem, but not something the script intends
|
||||
# to check for -- we're only making sure that things that are
|
||||
# executable have shebangs
|
||||
h = tmpdir.join('h')
|
||||
h.write('#!/usr/bin/env bash')
|
||||
h_path = str(h)
|
||||
cmd_output('git', 'add', h_path)
|
||||
|
||||
files = (f_path, g_path, h_path)
|
||||
assert check_executables_have_shebangs._check_git_filemode(files) == 0
|
||||
|
||||
|
||||
def test_check_git_filemode_passing_unusual_characters(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
cmd_output('git', 'init', '.')
|
||||
|
||||
f = tmpdir.join('mañana.txt')
|
||||
f.write('#!/usr/bin/env bash')
|
||||
f_path = str(f)
|
||||
cmd_output('chmod', '+x', f_path)
|
||||
cmd_output('git', 'add', f_path)
|
||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
||||
|
||||
files = (f_path,)
|
||||
assert check_executables_have_shebangs._check_git_filemode(files) == 0
|
||||
|
||||
|
||||
def test_check_git_filemode_failing(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
cmd_output('git', 'init', '.')
|
||||
|
||||
f = tmpdir.join('f').ensure()
|
||||
f_path = str(f)
|
||||
cmd_output('chmod', '+x', f_path)
|
||||
cmd_output('git', 'add', f_path)
|
||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
||||
|
||||
files = (f_path,)
|
||||
assert check_executables_have_shebangs._check_git_filemode(files) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('content', 'mode', 'expected'),
|
||||
(
|
||||
pytest.param('#!python', '+x', 0, id='shebang with executable'),
|
||||
pytest.param('#!python', '-x', 0, id='shebang without executable'),
|
||||
pytest.param('', '+x', 1, id='no shebang with executable'),
|
||||
pytest.param('', '-x', 0, id='no shebang without executable'),
|
||||
),
|
||||
)
|
||||
def test_git_executable_shebang(temp_git_dir, content, mode, expected):
|
||||
with temp_git_dir.as_cwd():
|
||||
path = temp_git_dir.join('path')
|
||||
path.write(content)
|
||||
cmd_output('git', 'add', str(path))
|
||||
cmd_output('chmod', mode, str(path))
|
||||
cmd_output('git', 'update-index', f'--chmod={mode}', str(path))
|
||||
|
||||
# simulate how identify chooses that something is executable
|
||||
filenames = [path for path in [str(path)] if os.access(path, os.X_OK)]
|
||||
|
||||
assert main(filenames) == expected
|
28
tests/check_json_test.py
Normal file
28
tests/check_json_test.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_json import main
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_retval'), (
|
||||
('bad_json.notjson', 1),
|
||||
('bad_json_latin1.nonjson', 1),
|
||||
('ok_json.json', 0),
|
||||
('duplicate_key_json.notjson', 1),
|
||||
),
|
||||
)
|
||||
def test_main(capsys, filename, expected_retval):
|
||||
ret = main([get_resource_path(filename)])
|
||||
assert ret == expected_retval
|
||||
if expected_retval == 1:
|
||||
stdout, _ = capsys.readouterr()
|
||||
assert filename in stdout
|
||||
|
||||
|
||||
def test_non_utf8_file(tmpdir):
|
||||
f = tmpdir.join('t.json')
|
||||
f.write_binary(b'\xa9\xfe\x12')
|
||||
assert main((str(f),))
|
157
tests/check_merge_conflict_test.py
Normal file
157
tests/check_merge_conflict_test.py
Normal file
|
@ -0,0 +1,157 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_merge_conflict import main
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from testing.util import get_resource_path
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def f1_is_a_conflict_file(tmpdir):
|
||||
# Make a merge conflict
|
||||
repo1 = tmpdir.join('repo1')
|
||||
repo1_f1 = repo1.join('f1')
|
||||
repo2 = tmpdir.join('repo2')
|
||||
repo2_f1 = repo2.join('f1')
|
||||
|
||||
cmd_output('git', 'init', '--', str(repo1))
|
||||
with repo1.as_cwd():
|
||||
repo1_f1.ensure()
|
||||
cmd_output('git', 'add', '.')
|
||||
git_commit('-m', 'commit1')
|
||||
|
||||
cmd_output('git', 'clone', str(repo1), str(repo2))
|
||||
|
||||
# Commit in master
|
||||
with repo1.as_cwd():
|
||||
repo1_f1.write('parent\n')
|
||||
git_commit('-am', 'master commit2')
|
||||
|
||||
# Commit in clone and pull
|
||||
with repo2.as_cwd():
|
||||
repo2_f1.write('child\n')
|
||||
git_commit('-am', 'clone commit2')
|
||||
cmd_output('git', 'pull', '--no-rebase', retcode=None)
|
||||
# We should end up in a merge conflict!
|
||||
f1 = repo2_f1.read()
|
||||
assert f1.startswith(
|
||||
'<<<<<<< HEAD\n'
|
||||
'child\n'
|
||||
'=======\n'
|
||||
'parent\n'
|
||||
'>>>>>>>',
|
||||
) or f1.startswith(
|
||||
'<<<<<<< HEAD\n'
|
||||
'child\n'
|
||||
# diff3 conflict style git merges add this line:
|
||||
'||||||| merged common ancestors\n'
|
||||
'=======\n'
|
||||
'parent\n'
|
||||
'>>>>>>>',
|
||||
) or f1.startswith(
|
||||
# .gitconfig with [pull] rebase = preserve causes a rebase which
|
||||
# flips parent / child
|
||||
'<<<<<<< HEAD\n'
|
||||
'parent\n'
|
||||
'=======\n'
|
||||
'child\n'
|
||||
'>>>>>>>',
|
||||
)
|
||||
assert os.path.exists(os.path.join('.git', 'MERGE_MSG'))
|
||||
yield repo2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repository_pending_merge(tmpdir):
|
||||
# Make a (non-conflicting) merge
|
||||
repo1 = tmpdir.join('repo1')
|
||||
repo1_f1 = repo1.join('f1')
|
||||
repo2 = tmpdir.join('repo2')
|
||||
repo2_f1 = repo2.join('f1')
|
||||
repo2_f2 = repo2.join('f2')
|
||||
cmd_output('git', 'init', str(repo1))
|
||||
with repo1.as_cwd():
|
||||
repo1_f1.ensure()
|
||||
cmd_output('git', 'add', '.')
|
||||
git_commit('-m', 'commit1')
|
||||
|
||||
cmd_output('git', 'clone', str(repo1), str(repo2))
|
||||
|
||||
# Commit in master
|
||||
with repo1.as_cwd():
|
||||
repo1_f1.write('parent\n')
|
||||
git_commit('-am', 'master commit2')
|
||||
|
||||
# Commit in clone and pull without committing
|
||||
with repo2.as_cwd():
|
||||
repo2_f2.write('child\n')
|
||||
cmd_output('git', 'add', '.')
|
||||
git_commit('-m', 'clone commit2')
|
||||
cmd_output('git', 'pull', '--no-commit', '--no-rebase')
|
||||
# We should end up in a pending merge
|
||||
assert repo2_f1.read() == 'parent\n'
|
||||
assert repo2_f2.read() == 'child\n'
|
||||
assert os.path.exists(os.path.join('.git', 'MERGE_HEAD'))
|
||||
yield repo2
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('f1_is_a_conflict_file')
|
||||
def test_merge_conflicts_git(capsys):
|
||||
assert main(['f1']) == 1
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == (
|
||||
"f1:1: Merge conflict string '<<<<<<<' found\n"
|
||||
"f1:3: Merge conflict string '=======' found\n"
|
||||
"f1:5: Merge conflict string '>>>>>>>' found\n"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> master\n'),
|
||||
)
|
||||
def test_merge_conflicts_failing(contents, repository_pending_merge):
|
||||
repository_pending_merge.join('f2').write_binary(contents)
|
||||
assert main(['f2']) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'contents', (b'# <<<<<<< HEAD\n', b'# =======\n', b'import mod', b''),
|
||||
)
|
||||
def test_merge_conflicts_ok(contents, f1_is_a_conflict_file):
|
||||
f1_is_a_conflict_file.join('f1').write_binary(contents)
|
||||
assert main(['f1']) == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('f1_is_a_conflict_file')
|
||||
def test_ignores_binary_files():
|
||||
shutil.copy(get_resource_path('img1.jpg'), 'f1')
|
||||
assert main(['f1']) == 0
|
||||
|
||||
|
||||
def test_does_not_care_when_not_in_a_merge(tmpdir):
|
||||
f = tmpdir.join('README.md')
|
||||
f.write_binary(b'problem\n=======\n')
|
||||
assert main([str(f.realpath())]) == 0
|
||||
|
||||
|
||||
def test_care_when_assumed_merge(tmpdir):
|
||||
f = tmpdir.join('README.md')
|
||||
f.write_binary(b'problem\n=======\n')
|
||||
assert main([str(f.realpath()), '--assume-in-merge']) == 1
|
||||
|
||||
|
||||
def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir, capsys):
|
||||
worktree = tmpdir.join('worktree')
|
||||
cmd_output('git', 'worktree', 'add', str(worktree))
|
||||
with worktree.as_cwd():
|
||||
cmd_output(
|
||||
'git', 'pull', '--no-rebase', 'origin', 'master', retcode=None,
|
||||
)
|
||||
msg = f1_is_a_conflict_file.join('.git/worktrees/worktree/MERGE_MSG')
|
||||
assert msg.exists()
|
||||
test_merge_conflicts_git(capsys)
|
89
tests/check_shebang_scripts_are_executable_test.py
Normal file
89
tests/check_shebang_scripts_are_executable_test.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_shebang_scripts_are_executable import \
|
||||
_check_git_filemode
|
||||
from pre_commit_hooks.check_shebang_scripts_are_executable import main
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
|
||||
|
||||
def test_check_git_filemode_passing(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
cmd_output('git', 'init', '.')
|
||||
|
||||
f = tmpdir.join('f')
|
||||
f.write('#!/usr/bin/env bash')
|
||||
f_path = str(f)
|
||||
cmd_output('chmod', '+x', f_path)
|
||||
cmd_output('git', 'add', f_path)
|
||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
||||
|
||||
g = tmpdir.join('g').ensure()
|
||||
g_path = str(g)
|
||||
cmd_output('git', 'add', g_path)
|
||||
|
||||
files = [f_path, g_path]
|
||||
assert _check_git_filemode(files) == 0
|
||||
|
||||
# this is the one we should trigger on
|
||||
h = tmpdir.join('h')
|
||||
h.write('#!/usr/bin/env bash')
|
||||
h_path = str(h)
|
||||
cmd_output('git', 'add', h_path)
|
||||
|
||||
files = [h_path]
|
||||
assert _check_git_filemode(files) == 1
|
||||
|
||||
|
||||
def test_check_git_filemode_passing_unusual_characters(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
cmd_output('git', 'init', '.')
|
||||
|
||||
f = tmpdir.join('mañana.txt')
|
||||
f.write('#!/usr/bin/env bash')
|
||||
f_path = str(f)
|
||||
cmd_output('chmod', '+x', f_path)
|
||||
cmd_output('git', 'add', f_path)
|
||||
cmd_output('git', 'update-index', '--chmod=+x', f_path)
|
||||
|
||||
files = (f_path,)
|
||||
assert _check_git_filemode(files) == 0
|
||||
|
||||
|
||||
def test_check_git_filemode_failing(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
cmd_output('git', 'init', '.')
|
||||
|
||||
f = tmpdir.join('f').ensure()
|
||||
f.write('#!/usr/bin/env bash')
|
||||
f_path = str(f)
|
||||
cmd_output('git', 'add', f_path)
|
||||
|
||||
files = (f_path,)
|
||||
assert _check_git_filemode(files) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('content', 'mode', 'expected'),
|
||||
(
|
||||
pytest.param('#!python', '+x', 0, id='shebang with executable'),
|
||||
pytest.param('#!python', '-x', 1, id='shebang without executable'),
|
||||
pytest.param('', '+x', 0, id='no shebang with executable'),
|
||||
pytest.param('', '-x', 0, id='no shebang without executable'),
|
||||
),
|
||||
)
|
||||
def test_git_executable_shebang(temp_git_dir, content, mode, expected):
|
||||
with temp_git_dir.as_cwd():
|
||||
path = temp_git_dir.join('path')
|
||||
path.write(content)
|
||||
cmd_output('git', 'add', str(path))
|
||||
cmd_output('chmod', mode, str(path))
|
||||
cmd_output('git', 'update-index', f'--chmod={mode}', str(path))
|
||||
|
||||
# simulate how identify chooses that something is executable
|
||||
filenames = [path for path in [str(path)] if os.access(path, os.X_OK)]
|
||||
|
||||
assert main(filenames) == expected
|
25
tests/check_symlinks_test.py
Normal file
25
tests/check_symlinks_test.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_symlinks import main
|
||||
|
||||
|
||||
xfail_symlink = pytest.mark.xfail(os.name == 'nt', reason='No symlink support')
|
||||
|
||||
|
||||
@xfail_symlink
|
||||
@pytest.mark.parametrize(
|
||||
('dest', 'expected'), (('exists', 0), ('does-not-exist', 1)),
|
||||
)
|
||||
def test_main(tmpdir, dest, expected): # pragma: no cover (symlinks)
|
||||
tmpdir.join('exists').ensure()
|
||||
symlink = tmpdir.join('symlink')
|
||||
symlink.mksymlinkto(tmpdir.join(dest))
|
||||
assert main((str(symlink),)) == expected
|
||||
|
||||
|
||||
def test_main_normal_file(tmpdir):
|
||||
assert main((str(tmpdir.join('f').ensure()),)) == 0
|
38
tests/check_toml_test.py
Normal file
38
tests/check_toml_test.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_toml import main
|
||||
|
||||
|
||||
def test_toml_bad(tmpdir):
|
||||
filename = tmpdir.join('f')
|
||||
filename.write("""
|
||||
key = # INVALID
|
||||
|
||||
= "no key name" # INVALID
|
||||
""")
|
||||
ret = main((str(filename),))
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_toml_good(tmpdir):
|
||||
filename = tmpdir.join('f')
|
||||
filename.write(
|
||||
"""
|
||||
# This is a TOML document.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "John"
|
||||
dob = 1979-05-27T07:32:00-08:00 # First class dates
|
||||
""",
|
||||
)
|
||||
ret = main((str(filename),))
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_toml_good_unicode(tmpdir):
|
||||
filename = tmpdir.join('f')
|
||||
filename.write_binary('letter = "\N{SNOWMAN}"\n'.encode())
|
||||
ret = main((str(filename),))
|
||||
assert ret == 0
|
43
tests/check_vcs_permalinks_test.py
Normal file
43
tests/check_vcs_permalinks_test.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_vcs_permalinks import main
|
||||
|
||||
|
||||
def test_trivial(tmpdir):
|
||||
f = tmpdir.join('f.txt').ensure()
|
||||
assert not main((str(f),))
|
||||
|
||||
|
||||
def test_passing(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_binary(
|
||||
# permalinks are ok
|
||||
b'https://github.com/asottile/test/blob/649e6/foo%20bar#L1\n'
|
||||
# tags are ok
|
||||
b'https://github.com/asottile/test/blob/1.0.0/foo%20bar#L1\n'
|
||||
# links to files but not line numbers are ok
|
||||
b'https://github.com/asottile/test/blob/master/foo%20bar\n'
|
||||
# regression test for overly-greedy regex
|
||||
b'https://github.com/ yes / no ? /blob/master/foo#L1\n',
|
||||
)
|
||||
assert not main((str(f),))
|
||||
|
||||
|
||||
def test_failing(tmpdir, capsys):
|
||||
with tmpdir.as_cwd():
|
||||
tmpdir.join('f.txt').write_binary(
|
||||
b'https://github.com/asottile/test/blob/master/foo#L1\n'
|
||||
b'https://example.com/asottile/test/blob/master/foo#L1\n'
|
||||
b'https://example.com/asottile/test/blob/main/foo#L1\n',
|
||||
)
|
||||
|
||||
assert main(('f.txt', '--additional-github-domain', 'example.com'))
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == (
|
||||
'f.txt:1:https://github.com/asottile/test/blob/master/foo#L1\n'
|
||||
'f.txt:2:https://example.com/asottile/test/blob/master/foo#L1\n'
|
||||
'f.txt:3:https://example.com/asottile/test/blob/main/foo#L1\n'
|
||||
'\n'
|
||||
'Non-permanent github link detected.\n'
|
||||
'On any page on github press [y] to load a permalink.\n'
|
||||
)
|
17
tests/check_xml_test.py
Normal file
17
tests/check_xml_test.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_xml import main
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_retval'), (
|
||||
('bad_xml.notxml', 1),
|
||||
('ok_xml.xml', 0),
|
||||
),
|
||||
)
|
||||
def test_main(filename, expected_retval):
|
||||
ret = main([get_resource_path(filename)])
|
||||
assert ret == expected_retval
|
53
tests/check_yaml_test.py
Normal file
53
tests/check_yaml_test.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.check_yaml import main
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_retval'), (
|
||||
('bad_yaml.notyaml', 1),
|
||||
('ok_yaml.yaml', 0),
|
||||
),
|
||||
)
|
||||
def test_main(filename, expected_retval):
|
||||
ret = main([get_resource_path(filename)])
|
||||
assert ret == expected_retval
|
||||
|
||||
|
||||
def test_main_allow_multiple_documents(tmpdir):
|
||||
f = tmpdir.join('test.yaml')
|
||||
f.write('---\nfoo\n---\nbar\n')
|
||||
|
||||
# should fail without the setting
|
||||
assert main((str(f),))
|
||||
|
||||
# should pass when we allow multiple documents
|
||||
assert not main(('--allow-multiple-documents', str(f)))
|
||||
|
||||
|
||||
def test_fails_even_with_allow_multiple_documents(tmpdir):
|
||||
f = tmpdir.join('test.yaml')
|
||||
f.write('[')
|
||||
assert main(('--allow-multiple-documents', str(f)))
|
||||
|
||||
|
||||
def test_main_unsafe(tmpdir):
|
||||
f = tmpdir.join('test.yaml')
|
||||
f.write(
|
||||
'some_foo: !vault |\n'
|
||||
' $ANSIBLE_VAULT;1.1;AES256\n'
|
||||
' deadbeefdeadbeefdeadbeef\n',
|
||||
)
|
||||
# should fail "safe" check
|
||||
assert main((str(f),))
|
||||
# should pass when we allow unsafe documents
|
||||
assert not main(('--unsafe', str(f)))
|
||||
|
||||
|
||||
def test_main_unsafe_still_fails_on_syntax_errors(tmpdir):
|
||||
f = tmpdir.join('test.yaml')
|
||||
f.write('[')
|
||||
assert main(('--unsafe', str(f)))
|
12
tests/conftest.py
Normal file
12
tests/conftest.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_git_dir(tmpdir):
|
||||
git_dir = tmpdir.join('gits')
|
||||
cmd_output('git', 'init', '--', str(git_dir))
|
||||
yield git_dir
|
63
tests/debug_statement_hook_test.py
Normal file
63
tests/debug_statement_hook_test.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
|
||||
from pre_commit_hooks.debug_statement_hook import Debug
|
||||
from pre_commit_hooks.debug_statement_hook import DebugStatementParser
|
||||
from pre_commit_hooks.debug_statement_hook import main
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
||||
def test_no_breakpoints():
|
||||
visitor = DebugStatementParser()
|
||||
visitor.visit(ast.parse('import os\nfrom foo import bar\n'))
|
||||
assert visitor.breakpoints == []
|
||||
|
||||
|
||||
def test_finds_debug_import_attribute_access():
|
||||
visitor = DebugStatementParser()
|
||||
visitor.visit(ast.parse('import ipdb; ipdb.set_trace()'))
|
||||
assert visitor.breakpoints == [Debug(1, 0, 'ipdb', 'imported')]
|
||||
|
||||
|
||||
def test_finds_debug_import_from_import():
|
||||
visitor = DebugStatementParser()
|
||||
visitor.visit(ast.parse('from pudb import set_trace; set_trace()'))
|
||||
assert visitor.breakpoints == [Debug(1, 0, 'pudb', 'imported')]
|
||||
|
||||
|
||||
def test_finds_breakpoint():
|
||||
visitor = DebugStatementParser()
|
||||
visitor.visit(ast.parse('breakpoint()'))
|
||||
assert visitor.breakpoints == [Debug(1, 0, 'breakpoint', 'called')]
|
||||
|
||||
|
||||
def test_returns_one_for_failing_file(tmpdir):
|
||||
f_py = tmpdir.join('f.py')
|
||||
f_py.write('def f():\n import pdb; pdb.set_trace()')
|
||||
ret = main([str(f_py)])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_returns_zero_for_passing_file():
|
||||
ret = main([__file__])
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_syntaxerror_file():
|
||||
ret = main([get_resource_path('cannot_parse_ast.notpy')])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_non_utf8_file(tmpdir):
|
||||
f_py = tmpdir.join('f.py')
|
||||
f_py.write_binary('# -*- coding: cp1252 -*-\nx = "€"\n'.encode('cp1252'))
|
||||
assert main((str(f_py),)) == 0
|
||||
|
||||
|
||||
def test_py37_breakpoint(tmpdir, capsys):
|
||||
f_py = tmpdir.join('f.py')
|
||||
f_py.write('def f():\n breakpoint()\n')
|
||||
assert main((str(f_py),)) == 1
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == f'{f_py}:2:4: breakpoint called\n'
|
75
tests/destroyed_symlinks_test.py
Normal file
75
tests/destroyed_symlinks_test.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.destroyed_symlinks import find_destroyed_symlinks
|
||||
from pre_commit_hooks.destroyed_symlinks import main
|
||||
from testing.util import git_commit
|
||||
|
||||
TEST_SYMLINK = 'test_symlink'
|
||||
TEST_SYMLINK_TARGET = '/doesnt/really/matters'
|
||||
TEST_FILE = 'test_file'
|
||||
TEST_FILE_RENAMED = f'{TEST_FILE}_renamed'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repo_with_destroyed_symlink(tmpdir):
|
||||
source_repo = tmpdir.join('src')
|
||||
os.makedirs(source_repo, exist_ok=True)
|
||||
test_repo = tmpdir.join('test')
|
||||
with source_repo.as_cwd():
|
||||
subprocess.check_call(('git', 'init'))
|
||||
os.symlink(TEST_SYMLINK_TARGET, TEST_SYMLINK)
|
||||
with open(TEST_FILE, 'w') as f:
|
||||
print('some random content', file=f)
|
||||
subprocess.check_call(('git', 'add', '.'))
|
||||
git_commit('-m', 'initial')
|
||||
assert b'120000 ' in subprocess.check_output(
|
||||
('git', 'cat-file', '-p', 'HEAD^{tree}'),
|
||||
)
|
||||
subprocess.check_call(
|
||||
('git', '-c', 'core.symlinks=false', 'clone', source_repo, test_repo),
|
||||
)
|
||||
with test_repo.as_cwd():
|
||||
subprocess.check_call(
|
||||
('git', 'config', '--local', 'core.symlinks', 'true'),
|
||||
)
|
||||
subprocess.check_call(('git', 'mv', TEST_FILE, TEST_FILE_RENAMED))
|
||||
assert not os.path.islink(test_repo.join(TEST_SYMLINK))
|
||||
yield test_repo
|
||||
|
||||
|
||||
def test_find_destroyed_symlinks(repo_with_destroyed_symlink):
|
||||
with repo_with_destroyed_symlink.as_cwd():
|
||||
assert find_destroyed_symlinks([]) == []
|
||||
assert main([]) == 0
|
||||
|
||||
subprocess.check_call(('git', 'add', TEST_SYMLINK))
|
||||
assert find_destroyed_symlinks([TEST_SYMLINK]) == [TEST_SYMLINK]
|
||||
assert find_destroyed_symlinks([]) == []
|
||||
assert main([]) == 0
|
||||
assert find_destroyed_symlinks([TEST_FILE_RENAMED, TEST_FILE]) == []
|
||||
ALL_STAGED = [TEST_SYMLINK, TEST_FILE_RENAMED]
|
||||
assert find_destroyed_symlinks(ALL_STAGED) == [TEST_SYMLINK]
|
||||
assert main(ALL_STAGED) != 0
|
||||
|
||||
with open(TEST_SYMLINK, 'a') as f:
|
||||
print(file=f) # add trailing newline
|
||||
subprocess.check_call(['git', 'add', TEST_SYMLINK])
|
||||
assert find_destroyed_symlinks(ALL_STAGED) == [TEST_SYMLINK]
|
||||
assert main(ALL_STAGED) != 0
|
||||
|
||||
with open(TEST_SYMLINK, 'w') as f:
|
||||
print('0' * len(TEST_SYMLINK_TARGET), file=f)
|
||||
subprocess.check_call(('git', 'add', TEST_SYMLINK))
|
||||
assert find_destroyed_symlinks(ALL_STAGED) == []
|
||||
assert main(ALL_STAGED) == 0
|
||||
|
||||
with open(TEST_SYMLINK, 'w') as f:
|
||||
print('0' * (len(TEST_SYMLINK_TARGET) + 3), file=f)
|
||||
subprocess.check_call(('git', 'add', TEST_SYMLINK))
|
||||
assert find_destroyed_symlinks(ALL_STAGED) == []
|
||||
assert main(ALL_STAGED) == 0
|
170
tests/detect_aws_credentials_test.py
Normal file
170
tests/detect_aws_credentials_test.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.detect_aws_credentials import get_aws_cred_files_from_env
|
||||
from pre_commit_hooks.detect_aws_credentials import get_aws_secrets_from_env
|
||||
from pre_commit_hooks.detect_aws_credentials import get_aws_secrets_from_file
|
||||
from pre_commit_hooks.detect_aws_credentials import main
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('env_vars', 'values'),
|
||||
(
|
||||
({}, set()),
|
||||
({'AWS_PLACEHOLDER_KEY': '/foo'}, set()),
|
||||
({'AWS_CONFIG_FILE': '/foo'}, {'/foo'}),
|
||||
({'AWS_CREDENTIAL_FILE': '/foo'}, {'/foo'}),
|
||||
({'AWS_SHARED_CREDENTIALS_FILE': '/foo'}, {'/foo'}),
|
||||
({'BOTO_CONFIG': '/foo'}, {'/foo'}),
|
||||
({'AWS_PLACEHOLDER_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar'}, {'/bar'}),
|
||||
(
|
||||
{
|
||||
'AWS_PLACEHOLDER_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar',
|
||||
'AWS_CREDENTIAL_FILE': '/baz',
|
||||
},
|
||||
{'/bar', '/baz'},
|
||||
),
|
||||
(
|
||||
{
|
||||
'AWS_CONFIG_FILE': '/foo', 'AWS_CREDENTIAL_FILE': '/bar',
|
||||
'AWS_SHARED_CREDENTIALS_FILE': '/baz',
|
||||
},
|
||||
{'/foo', '/bar', '/baz'},
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_get_aws_credentials_file_from_env(env_vars, values):
|
||||
with patch.dict('os.environ', env_vars, clear=True):
|
||||
assert get_aws_cred_files_from_env() == values
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('env_vars', 'values'),
|
||||
(
|
||||
({}, set()),
|
||||
({'AWS_PLACEHOLDER_KEY': 'foo'}, set()),
|
||||
({'AWS_SECRET_ACCESS_KEY': 'foo'}, {'foo'}),
|
||||
({'AWS_SECURITY_TOKEN': 'foo'}, {'foo'}),
|
||||
({'AWS_SESSION_TOKEN': 'foo'}, {'foo'}),
|
||||
({'AWS_SESSION_TOKEN': ''}, set()),
|
||||
({'AWS_SESSION_TOKEN': 'foo', 'AWS_SECURITY_TOKEN': ''}, {'foo'}),
|
||||
(
|
||||
{'AWS_PLACEHOLDER_KEY': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar'},
|
||||
{'bar'},
|
||||
),
|
||||
(
|
||||
{'AWS_SECRET_ACCESS_KEY': 'foo', 'AWS_SECURITY_TOKEN': 'bar'},
|
||||
{'foo', 'bar'},
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_get_aws_secrets_from_env(env_vars, values):
|
||||
"""Test that reading secrets from environment variables works."""
|
||||
with patch.dict('os.environ', env_vars, clear=True):
|
||||
assert get_aws_secrets_from_env() == values
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_keys'),
|
||||
(
|
||||
(
|
||||
'aws_config_with_secret.ini',
|
||||
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'},
|
||||
),
|
||||
('aws_config_with_session_token.ini', {'foo'}),
|
||||
(
|
||||
'aws_config_with_secret_and_session_token.ini',
|
||||
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'foo'},
|
||||
),
|
||||
(
|
||||
'aws_config_with_multiple_sections.ini',
|
||||
{
|
||||
'7xebzorgm5143ouge9gvepxb2z70bsb2rtrh099e',
|
||||
'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb',
|
||||
'ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez',
|
||||
'foo',
|
||||
},
|
||||
),
|
||||
('aws_config_without_secrets.ini', set()),
|
||||
('aws_config_without_secrets_with_spaces.ini', set()),
|
||||
('nonsense.txt', set()),
|
||||
('ok_json.json', set()),
|
||||
),
|
||||
)
|
||||
def test_get_aws_secrets_from_file(filename, expected_keys):
|
||||
"""Test that reading secrets from files works."""
|
||||
keys = get_aws_secrets_from_file(get_resource_path(filename))
|
||||
assert keys == expected_keys
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_retval'),
|
||||
(
|
||||
('aws_config_with_secret.ini', 1),
|
||||
('aws_config_with_session_token.ini', 1),
|
||||
('aws_config_with_multiple_sections.ini', 1),
|
||||
('aws_config_without_secrets.ini', 0),
|
||||
('aws_config_without_secrets_with_spaces.ini', 0),
|
||||
('nonsense.txt', 0),
|
||||
('ok_json.json', 0),
|
||||
),
|
||||
)
|
||||
def test_detect_aws_credentials(filename, expected_retval):
|
||||
# with a valid credentials file
|
||||
ret = main((
|
||||
get_resource_path(filename),
|
||||
'--credentials-file',
|
||||
'testing/resources/aws_config_with_multiple_sections.ini',
|
||||
))
|
||||
assert ret == expected_retval
|
||||
|
||||
|
||||
def test_allows_arbitrarily_encoded_files(tmpdir):
|
||||
src_ini = tmpdir.join('src.ini')
|
||||
src_ini.write(
|
||||
'[default]\n'
|
||||
'aws_access_key_id=AKIASDFASDF\n'
|
||||
'aws_secret_Access_key=9018asdf23908190238123\n',
|
||||
)
|
||||
arbitrary_encoding = tmpdir.join('f')
|
||||
arbitrary_encoding.write_binary(b'\x12\x9a\xe2\xf2')
|
||||
ret = main((str(arbitrary_encoding), '--credentials-file', str(src_ini)))
|
||||
assert ret == 0
|
||||
|
||||
|
||||
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_file')
|
||||
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_env')
|
||||
def test_non_existent_credentials(mock_secrets_env, mock_secrets_file, capsys):
|
||||
"""Test behavior with no configured AWS secrets."""
|
||||
mock_secrets_env.return_value = set()
|
||||
mock_secrets_file.return_value = set()
|
||||
ret = main((
|
||||
get_resource_path('aws_config_without_secrets.ini'),
|
||||
'--credentials-file=testing/resources/credentailsfilethatdoesntexist',
|
||||
))
|
||||
assert ret == 2
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == (
|
||||
'No AWS keys were found in the configured credential files '
|
||||
'and environment variables.\nPlease ensure you have the '
|
||||
'correct setting for --credentials-file\n'
|
||||
)
|
||||
|
||||
|
||||
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_file')
|
||||
@patch('pre_commit_hooks.detect_aws_credentials.get_aws_secrets_from_env')
|
||||
def test_non_existent_credentials_with_allow_flag(
|
||||
mock_secrets_env, mock_secrets_file,
|
||||
):
|
||||
mock_secrets_env.return_value = set()
|
||||
mock_secrets_file.return_value = set()
|
||||
ret = main((
|
||||
get_resource_path('aws_config_without_secrets.ini'),
|
||||
'--credentials-file=testing/resources/credentailsfilethatdoesntexist',
|
||||
'--allow-missing-credentials',
|
||||
))
|
||||
assert ret == 0
|
28
tests/detect_private_key_test.py
Normal file
28
tests/detect_private_key_test.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.detect_private_key import main
|
||||
|
||||
# Input, expected return value
|
||||
TESTS = (
|
||||
(b'-----BEGIN RSA PRIVATE KEY-----', 1),
|
||||
(b'-----BEGIN DSA PRIVATE KEY-----', 1),
|
||||
(b'-----BEGIN EC PRIVATE KEY-----', 1),
|
||||
(b'-----BEGIN OPENSSH PRIVATE KEY-----', 1),
|
||||
(b'PuTTY-User-Key-File-2: ssh-rsa', 1),
|
||||
(b'---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----', 1),
|
||||
(b'-----BEGIN ENCRYPTED PRIVATE KEY-----', 1),
|
||||
(b'-----BEGIN OpenVPN Static key V1-----', 1),
|
||||
(b'ssh-rsa DATA', 0),
|
||||
(b'ssh-dsa DATA', 0),
|
||||
# Some arbitrary binary data
|
||||
(b'\xa2\xf1\x93\x12', 0),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('input_s', 'expected_retval'), TESTS)
|
||||
def test_main(input_s, expected_retval, tmpdir):
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write_binary(input_s)
|
||||
assert main([str(path)]) == expected_retval
|
44
tests/end_of_file_fixer_test.py
Normal file
44
tests/end_of_file_fixer_test.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.end_of_file_fixer import fix_file
|
||||
from pre_commit_hooks.end_of_file_fixer import main
|
||||
|
||||
|
||||
# Input, expected return value, expected output
|
||||
TESTS = (
|
||||
(b'foo\n', 0, b'foo\n'),
|
||||
(b'', 0, b''),
|
||||
(b'\n\n', 1, b''),
|
||||
(b'\n\n\n\n', 1, b''),
|
||||
(b'foo', 1, b'foo\n'),
|
||||
(b'foo\n\n\n', 1, b'foo\n'),
|
||||
(b'\xe2\x98\x83', 1, b'\xe2\x98\x83\n'),
|
||||
(b'foo\r\n', 0, b'foo\r\n'),
|
||||
(b'foo\r\n\r\n\r\n', 1, b'foo\r\n'),
|
||||
(b'foo\r', 0, b'foo\r'),
|
||||
(b'foo\r\r\r\r', 1, b'foo\r'),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS)
|
||||
def test_fix_file(input_s, expected_retval, output):
|
||||
file_obj = io.BytesIO(input_s)
|
||||
ret = fix_file(file_obj)
|
||||
assert file_obj.getvalue() == output
|
||||
assert ret == expected_retval
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS)
|
||||
def test_integration(input_s, expected_retval, output, tmpdir):
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write_binary(input_s)
|
||||
|
||||
ret = main([str(path)])
|
||||
file_output = path.read_binary()
|
||||
|
||||
assert file_output == output
|
||||
assert ret == expected_retval
|
91
tests/file_contents_sorter_test.py
Normal file
91
tests/file_contents_sorter_test.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.file_contents_sorter import FAIL
|
||||
from pre_commit_hooks.file_contents_sorter import main
|
||||
from pre_commit_hooks.file_contents_sorter import PASS
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'argv', 'expected_retval', 'output'),
|
||||
(
|
||||
(b'', [], PASS, b''),
|
||||
(b'\n', [], FAIL, b''),
|
||||
(b'\n\n', [], FAIL, b''),
|
||||
(b'lonesome\n', [], PASS, b'lonesome\n'),
|
||||
(b'missing_newline', [], FAIL, b'missing_newline\n'),
|
||||
(b'newline\nmissing', [], FAIL, b'missing\nnewline\n'),
|
||||
(b'missing\nnewline', [], FAIL, b'missing\nnewline\n'),
|
||||
(b'alpha\nbeta\n', [], PASS, b'alpha\nbeta\n'),
|
||||
(b'beta\nalpha\n', [], FAIL, b'alpha\nbeta\n'),
|
||||
(b'C\nc\n', [], PASS, b'C\nc\n'),
|
||||
(b'c\nC\n', [], FAIL, b'C\nc\n'),
|
||||
(b'mag ical \n tre vor\n', [], FAIL, b' tre vor\nmag ical \n'),
|
||||
(b'@\n-\n_\n#\n', [], FAIL, b'#\n-\n@\n_\n'),
|
||||
(b'extra\n\n\nwhitespace\n', [], FAIL, b'extra\nwhitespace\n'),
|
||||
(b'whitespace\n\n\nextra\n', [], FAIL, b'extra\nwhitespace\n'),
|
||||
(
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
[],
|
||||
FAIL,
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
),
|
||||
(
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
[],
|
||||
PASS,
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
),
|
||||
(
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
['--ignore-case'],
|
||||
PASS,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
(
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
['--ignore-case'],
|
||||
FAIL,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
(
|
||||
b'Fie\nFoe\nfee\nfee\nfum\n',
|
||||
['--ignore-case'],
|
||||
FAIL,
|
||||
b'fee\nfee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
(
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
['--unique'],
|
||||
PASS,
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
),
|
||||
(
|
||||
b'Fie\nFie\nFoe\nfee\nfum\n',
|
||||
['--unique'],
|
||||
FAIL,
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
),
|
||||
(
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
PASS,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
(
|
||||
b'fee\nfee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
FAIL,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_integration(input_s, argv, expected_retval, output, tmpdir):
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write_binary(input_s)
|
||||
|
||||
output_retval = main([str(path)] + argv)
|
||||
|
||||
assert path.read_binary() == output
|
||||
assert output_retval == expected_retval
|
15
tests/fix_byte_order_marker_test.py
Normal file
15
tests/fix_byte_order_marker_test.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks import fix_byte_order_marker
|
||||
|
||||
|
||||
def test_failure(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_text('ohai', encoding='utf-8-sig')
|
||||
assert fix_byte_order_marker.main((str(f),)) == 1
|
||||
|
||||
|
||||
def test_success(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_text('ohai', encoding='utf-8')
|
||||
assert fix_byte_order_marker.main((str(f),)) == 0
|
161
tests/fix_encoding_pragma_test.py
Normal file
161
tests/fix_encoding_pragma_test.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.fix_encoding_pragma import _normalize_pragma
|
||||
from pre_commit_hooks.fix_encoding_pragma import fix_encoding_pragma
|
||||
from pre_commit_hooks.fix_encoding_pragma import main
|
||||
|
||||
|
||||
def test_integration_inserting_pragma(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'import httplib\n')
|
||||
|
||||
assert main((str(path),)) == 1
|
||||
|
||||
assert path.read_binary() == (
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'import httplib\n'
|
||||
)
|
||||
|
||||
|
||||
def test_integration_ok(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'# -*- coding: utf-8 -*-\nx = 1\n')
|
||||
assert main((str(path),)) == 0
|
||||
|
||||
|
||||
def test_integration_remove(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'# -*- coding: utf-8 -*-\nx = 1\n')
|
||||
|
||||
assert main((str(path), '--remove')) == 1
|
||||
|
||||
assert path.read_binary() == b'x = 1\n'
|
||||
|
||||
|
||||
def test_integration_remove_ok(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'x = 1\n')
|
||||
assert main((str(path), '--remove')) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'input_str',
|
||||
(
|
||||
b'',
|
||||
(
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n'
|
||||
),
|
||||
(
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'foo = "bar"\n'
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_ok_inputs(input_str):
|
||||
bytesio = io.BytesIO(input_str)
|
||||
assert fix_encoding_pragma(bytesio) == 0
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == input_str
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_str', 'output'),
|
||||
(
|
||||
(
|
||||
b'import httplib\n',
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'import httplib\n',
|
||||
),
|
||||
(
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'x = 1\n',
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n',
|
||||
),
|
||||
(
|
||||
b'#coding=utf-8\n'
|
||||
b'x = 1\n',
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n',
|
||||
),
|
||||
(
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'#coding=utf8\n'
|
||||
b'x = 1\n',
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n',
|
||||
),
|
||||
# These should each get truncated
|
||||
(b'#coding: utf-8\n', b''),
|
||||
(b'# -*- coding: utf-8 -*-\n', b''),
|
||||
(b'#!/usr/bin/env python\n', b''),
|
||||
(b'#!/usr/bin/env python\n#coding: utf8\n', b''),
|
||||
(b'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n', b''),
|
||||
),
|
||||
)
|
||||
def test_not_ok_inputs(input_str, output):
|
||||
bytesio = io.BytesIO(input_str)
|
||||
assert fix_encoding_pragma(bytesio) == 1
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == output
|
||||
|
||||
|
||||
def test_ok_input_alternate_pragma():
|
||||
input_s = b'# coding: utf-8\nx = 1\n'
|
||||
bytesio = io.BytesIO(input_s)
|
||||
ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8')
|
||||
assert ret == 0
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == input_s
|
||||
|
||||
|
||||
def test_not_ok_input_alternate_pragma():
|
||||
bytesio = io.BytesIO(b'x = 1\n')
|
||||
ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8')
|
||||
assert ret == 1
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == b'# coding: utf-8\nx = 1\n'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'expected'),
|
||||
(
|
||||
('# coding: utf-8', b'# coding: utf-8'),
|
||||
# trailing whitespace
|
||||
('# coding: utf-8\n', b'# coding: utf-8'),
|
||||
),
|
||||
)
|
||||
def test_normalize_pragma(input_s, expected):
|
||||
assert _normalize_pragma(input_s) == expected
|
||||
|
||||
|
||||
def test_integration_alternate_pragma(tmpdir, capsys):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write('x = 1\n')
|
||||
|
||||
pragma = '# coding: utf-8'
|
||||
assert main((str(f), '--pragma', pragma)) == 1
|
||||
assert f.read() == '# coding: utf-8\nx = 1\n'
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == f'Added `# coding: utf-8` to {str(f)}\n'
|
||||
|
||||
|
||||
def test_crlf_ok(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write_binary(b'# -*- coding: utf-8 -*-\r\nx = 1\r\n')
|
||||
assert not main((str(f),))
|
||||
|
||||
|
||||
def test_crfl_adds(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write_binary(b'x = 1\r\n')
|
||||
assert main((str(f),))
|
||||
assert f.read_binary() == b'# -*- coding: utf-8 -*-\r\nx = 1\r\n'
|
59
tests/forbid_new_submodules_test.py
Normal file
59
tests/forbid_new_submodules_test.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.forbid_new_submodules import main
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def git_dir_with_git_dir(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
subprocess.check_call(('git', 'init', '.'))
|
||||
git_commit('--allow-empty', '-m', 'init')
|
||||
subprocess.check_call(('git', 'init', 'foo'))
|
||||
git_commit('--allow-empty', '-m', 'init', cwd=str(tmpdir.join('foo')))
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'cmd',
|
||||
(
|
||||
# Actually add the submodule
|
||||
('git', 'submodule', 'add', './foo'),
|
||||
# Sneaky submodule add (that doesn't show up in .gitmodules)
|
||||
('git', 'add', 'foo'),
|
||||
),
|
||||
)
|
||||
def test_main_new_submodule(git_dir_with_git_dir, capsys, cmd):
|
||||
subprocess.check_call(cmd)
|
||||
assert main(('random_non-related_file',)) == 0
|
||||
assert main(('foo',)) == 1
|
||||
out, _ = capsys.readouterr()
|
||||
assert out.startswith('foo: new submodule introduced\n')
|
||||
|
||||
|
||||
def test_main_new_submodule_committed(git_dir_with_git_dir, capsys):
|
||||
rev_parse_cmd = ('git', 'rev-parse', 'HEAD')
|
||||
from_ref = subprocess.check_output(rev_parse_cmd).decode().strip()
|
||||
subprocess.check_call(('git', 'submodule', 'add', './foo'))
|
||||
git_commit('-m', 'new submodule')
|
||||
to_ref = subprocess.check_output(rev_parse_cmd).decode().strip()
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{'PRE_COMMIT_FROM_REF': from_ref, 'PRE_COMMIT_TO_REF': to_ref},
|
||||
):
|
||||
assert main(('random_non-related_file',)) == 0
|
||||
assert main(('foo',)) == 1
|
||||
out, _ = capsys.readouterr()
|
||||
assert out.startswith('foo: new submodule introduced\n')
|
||||
|
||||
|
||||
def test_main_no_new_submodule(git_dir_with_git_dir):
|
||||
open('test.py', 'a+').close()
|
||||
subprocess.check_call(('git', 'add', 'test.py'))
|
||||
assert main(('test.py',)) == 0
|
118
tests/mixed_line_ending_test.py
Normal file
118
tests/mixed_line_ending_test.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.mixed_line_ending import main
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'output'),
|
||||
(
|
||||
# mixed with majority of 'LF'
|
||||
(b'foo\r\nbar\nbaz\n', b'foo\nbar\nbaz\n'),
|
||||
# mixed with majority of 'CRLF'
|
||||
(b'foo\r\nbar\nbaz\r\n', b'foo\r\nbar\r\nbaz\r\n'),
|
||||
# mixed with majority of 'CR'
|
||||
(b'foo\rbar\nbaz\r', b'foo\rbar\rbaz\r'),
|
||||
# mixed with as much 'LF' as 'CRLF'
|
||||
(b'foo\r\nbar\n', b'foo\nbar\n'),
|
||||
# mixed with as much 'LF' as 'CR'
|
||||
(b'foo\rbar\n', b'foo\nbar\n'),
|
||||
# mixed with as much 'CRLF' as 'CR'
|
||||
(b'foo\r\nbar\r', b'foo\r\nbar\r\n'),
|
||||
# mixed with as much 'CRLF' as 'LF' as 'CR'
|
||||
(b'foo\r\nbar\nbaz\r', b'foo\nbar\nbaz\n'),
|
||||
),
|
||||
)
|
||||
def test_mixed_line_ending_fixes_auto(input_s, output, tmpdir):
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write_binary(input_s)
|
||||
ret = main((str(path),))
|
||||
|
||||
assert ret == 1
|
||||
assert path.read_binary() == output
|
||||
|
||||
|
||||
def test_non_mixed_no_newline_end_of_file(tmpdir):
|
||||
path = tmpdir.join('f.txt')
|
||||
path.write_binary(b'foo\nbar\nbaz')
|
||||
assert not main((str(path),))
|
||||
# the hook *could* fix the end of the file, but leaves it alone
|
||||
# this is mostly to document the current behaviour
|
||||
assert path.read_binary() == b'foo\nbar\nbaz'
|
||||
|
||||
|
||||
def test_mixed_no_newline_end_of_file(tmpdir):
|
||||
path = tmpdir.join('f.txt')
|
||||
path.write_binary(b'foo\r\nbar\nbaz')
|
||||
assert main((str(path),))
|
||||
# the hook rewrites the end of the file, this is slightly inconsistent
|
||||
# with the non-mixed case but I think this is the better behaviour
|
||||
# this is mostly to document the current behaviour
|
||||
assert path.read_binary() == b'foo\nbar\nbaz\n'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('fix_option', 'input_s'),
|
||||
(
|
||||
# All --fix=auto with uniform line endings should be ok
|
||||
('--fix=auto', b'foo\r\nbar\r\nbaz\r\n'),
|
||||
('--fix=auto', b'foo\rbar\rbaz\r'),
|
||||
('--fix=auto', b'foo\nbar\nbaz\n'),
|
||||
# --fix=crlf with crlf endings
|
||||
('--fix=crlf', b'foo\r\nbar\r\nbaz\r\n'),
|
||||
# --fix=lf with lf endings
|
||||
('--fix=lf', b'foo\nbar\nbaz\n'),
|
||||
),
|
||||
)
|
||||
def test_line_endings_ok(fix_option, input_s, tmpdir, capsys):
|
||||
path = tmpdir.join('input.txt')
|
||||
path.write_binary(input_s)
|
||||
ret = main((fix_option, str(path)))
|
||||
|
||||
assert ret == 0
|
||||
assert path.read_binary() == input_s
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == ''
|
||||
|
||||
|
||||
def test_no_fix_does_not_modify(tmpdir, capsys):
|
||||
path = tmpdir.join('input.txt')
|
||||
contents = b'foo\r\nbar\rbaz\nwomp\n'
|
||||
path.write_binary(contents)
|
||||
ret = main(('--fix=no', str(path)))
|
||||
|
||||
assert ret == 1
|
||||
assert path.read_binary() == contents
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == f'{path}: mixed line endings\n'
|
||||
|
||||
|
||||
def test_fix_lf(tmpdir, capsys):
|
||||
path = tmpdir.join('input.txt')
|
||||
path.write_binary(b'foo\r\nbar\rbaz\n')
|
||||
ret = main(('--fix=lf', str(path)))
|
||||
|
||||
assert ret == 1
|
||||
assert path.read_binary() == b'foo\nbar\nbaz\n'
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == f'{path}: fixed mixed line endings\n'
|
||||
|
||||
|
||||
def test_fix_crlf(tmpdir):
|
||||
path = tmpdir.join('input.txt')
|
||||
path.write_binary(b'foo\r\nbar\rbaz\n')
|
||||
ret = main(('--fix=crlf', str(path)))
|
||||
|
||||
assert ret == 1
|
||||
assert path.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
|
||||
|
||||
|
||||
def test_fix_lf_all_crlf(tmpdir):
|
||||
"""Regression test for #239"""
|
||||
path = tmpdir.join('input.txt')
|
||||
path.write_binary(b'foo\r\nbar\r\n')
|
||||
ret = main(('--fix=lf', str(path)))
|
||||
|
||||
assert ret == 1
|
||||
assert path.read_binary() == b'foo\nbar\n'
|
79
tests/no_commit_to_branch_test.py
Normal file
79
tests/no_commit_to_branch_test.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.no_commit_to_branch import is_on_branch
|
||||
from pre_commit_hooks.no_commit_to_branch import main
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from testing.util import git_commit
|
||||
|
||||
|
||||
def test_other_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'anotherbranch')
|
||||
assert is_on_branch({'master'}) is False
|
||||
|
||||
|
||||
def test_multi_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'another/branch')
|
||||
assert is_on_branch({'master'}) is False
|
||||
|
||||
|
||||
def test_multi_branch_fail(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'another/branch')
|
||||
assert is_on_branch({'another/branch'}) is True
|
||||
|
||||
|
||||
def test_master_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
assert is_on_branch({'master'}) is True
|
||||
|
||||
|
||||
def test_main_branch_call(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'other')
|
||||
assert main(('--branch', 'other')) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branch_name', ('b1', 'b2'))
|
||||
def test_forbid_multiple_branches(temp_git_dir, branch_name):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', branch_name)
|
||||
assert main(('--branch', 'b1', '--branch', 'b2'))
|
||||
|
||||
|
||||
def test_branch_pattern_fail(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'another/branch')
|
||||
assert is_on_branch(set(), {'another/.*'}) is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branch_name', ('master', 'another/branch'))
|
||||
def test_branch_pattern_multiple_branches_fail(temp_git_dir, branch_name):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', branch_name)
|
||||
assert main(('--branch', 'master', '--pattern', 'another/.*'))
|
||||
|
||||
|
||||
def test_main_default_call(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'anotherbranch')
|
||||
assert main(()) == 0
|
||||
|
||||
|
||||
def test_not_on_a_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
git_commit('--allow-empty', '-m1')
|
||||
head = cmd_output('git', 'rev-parse', 'HEAD').strip()
|
||||
cmd_output('git', 'checkout', head)
|
||||
# we're not on a branch!
|
||||
assert main(()) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branch_name', ('master', 'main'))
|
||||
def test_default_branch_names(temp_git_dir, branch_name):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', branch_name)
|
||||
assert main(()) == 1
|
139
tests/pretty_format_json_test.py
Normal file
139
tests/pretty_format_json_test.py
Normal file
|
@ -0,0 +1,139 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.pretty_format_json import main
|
||||
from pre_commit_hooks.pretty_format_json import parse_num_to_int
|
||||
from testing.util import get_resource_path
|
||||
|
||||
|
||||
def test_parse_num_to_int():
|
||||
assert parse_num_to_int('0') == 0
|
||||
assert parse_num_to_int('2') == 2
|
||||
assert parse_num_to_int('\t') == '\t'
|
||||
assert parse_num_to_int(' ') == ' '
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_retval'), (
|
||||
('not_pretty_formatted_json.json', 1),
|
||||
('unsorted_pretty_formatted_json.json', 1),
|
||||
('non_ascii_pretty_formatted_json.json', 1),
|
||||
('pretty_formatted_json.json', 0),
|
||||
),
|
||||
)
|
||||
def test_main(filename, expected_retval):
|
||||
ret = main([get_resource_path(filename)])
|
||||
assert ret == expected_retval
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_retval'), (
|
||||
('not_pretty_formatted_json.json', 1),
|
||||
('unsorted_pretty_formatted_json.json', 0),
|
||||
('non_ascii_pretty_formatted_json.json', 1),
|
||||
('pretty_formatted_json.json', 0),
|
||||
),
|
||||
)
|
||||
def test_unsorted_main(filename, expected_retval):
|
||||
ret = main(['--no-sort-keys', get_resource_path(filename)])
|
||||
assert ret == expected_retval
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('filename', 'expected_retval'), (
|
||||
('not_pretty_formatted_json.json', 1),
|
||||
('unsorted_pretty_formatted_json.json', 1),
|
||||
('non_ascii_pretty_formatted_json.json', 1),
|
||||
('pretty_formatted_json.json', 1),
|
||||
('tab_pretty_formatted_json.json', 0),
|
||||
),
|
||||
)
|
||||
def test_tab_main(filename, expected_retval):
|
||||
ret = main(['--indent', '\t', get_resource_path(filename)])
|
||||
assert ret == expected_retval
|
||||
|
||||
|
||||
def test_non_ascii_main():
|
||||
ret = main((
|
||||
'--no-ensure-ascii',
|
||||
get_resource_path('non_ascii_pretty_formatted_json.json'),
|
||||
))
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_autofix_main(tmpdir):
|
||||
srcfile = tmpdir.join('to_be_json_formatted.json')
|
||||
shutil.copyfile(
|
||||
get_resource_path('not_pretty_formatted_json.json'),
|
||||
str(srcfile),
|
||||
)
|
||||
|
||||
# now launch the autofix on that file
|
||||
ret = main(['--autofix', str(srcfile)])
|
||||
# it should have formatted it
|
||||
assert ret == 1
|
||||
|
||||
# file was formatted (shouldn't trigger linter again)
|
||||
ret = main([str(srcfile)])
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_orderfile_get_pretty_format():
|
||||
ret = main((
|
||||
'--top-keys=alist', get_resource_path('pretty_formatted_json.json'),
|
||||
))
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_not_orderfile_get_pretty_format():
|
||||
ret = main((
|
||||
'--top-keys=blah', get_resource_path('pretty_formatted_json.json'),
|
||||
))
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_top_sorted_get_pretty_format():
|
||||
ret = main((
|
||||
'--top-keys=01-alist,alist', get_resource_path('top_sorted_json.json'),
|
||||
))
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_badfile_main():
|
||||
ret = main([get_resource_path('ok_yaml.yaml')])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_diffing_output(capsys):
|
||||
resource_path = get_resource_path('not_pretty_formatted_json.json')
|
||||
expected_retval = 1
|
||||
a = os.path.join('a', resource_path)
|
||||
b = os.path.join('b', resource_path)
|
||||
expected_out = f'''\
|
||||
--- {a}
|
||||
+++ {b}
|
||||
@@ -1,6 +1,9 @@
|
||||
{{
|
||||
- "foo":
|
||||
- "bar",
|
||||
- "alist": [2, 34, 234],
|
||||
- "blah": null
|
||||
+ "alist": [
|
||||
+ 2,
|
||||
+ 34,
|
||||
+ 234
|
||||
+ ],
|
||||
+ "blah": null,
|
||||
+ "foo": "bar"
|
||||
}}
|
||||
'''
|
||||
actual_retval = main([resource_path])
|
||||
actual_out, actual_err = capsys.readouterr()
|
||||
|
||||
assert actual_retval == expected_retval
|
||||
assert actual_out == expected_out
|
||||
assert actual_err == ''
|
12
tests/readme_test.py
Normal file
12
tests/readme_test.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.check_yaml import yaml
|
||||
|
||||
|
||||
def test_readme_contains_all_hooks():
|
||||
with open('README.md', encoding='UTF-8') as f:
|
||||
readme_contents = f.read()
|
||||
with open('.pre-commit-hooks.yaml', encoding='UTF-8') as f:
|
||||
hooks = yaml.load(f)
|
||||
for hook in hooks:
|
||||
assert f'`{hook["id"]}`' in readme_contents
|
19
tests/removed_test.py
Normal file
19
tests/removed_test.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.removed import main
|
||||
|
||||
|
||||
def test_always_fails():
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
main((
|
||||
'autopep8-wrapper', 'autopep8',
|
||||
'https://github.com/pre-commit/mirrors-autopep8',
|
||||
'--foo', 'bar',
|
||||
))
|
||||
msg, = excinfo.value.args
|
||||
assert msg == (
|
||||
'`autopep8-wrapper` has been removed -- '
|
||||
'use `autopep8` from https://github.com/pre-commit/mirrors-autopep8'
|
||||
)
|
130
tests/requirements_txt_fixer_test.py
Normal file
130
tests/requirements_txt_fixer_test.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.requirements_txt_fixer import FAIL
|
||||
from pre_commit_hooks.requirements_txt_fixer import main
|
||||
from pre_commit_hooks.requirements_txt_fixer import PASS
|
||||
from pre_commit_hooks.requirements_txt_fixer import Requirement
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'expected_retval', 'output'),
|
||||
(
|
||||
(b'', PASS, b''),
|
||||
(b'\n', PASS, b'\n'),
|
||||
(b'# intentionally empty\n', PASS, b'# intentionally empty\n'),
|
||||
(b'foo\n# comment at end\n', PASS, b'foo\n# comment at end\n'),
|
||||
(b'foo\nbar\n', FAIL, b'bar\nfoo\n'),
|
||||
(b'bar\nfoo\n', PASS, b'bar\nfoo\n'),
|
||||
(b'a\nc\nb\n', FAIL, b'a\nb\nc\n'),
|
||||
(b'a\nc\nb', FAIL, b'a\nb\nc\n'),
|
||||
(b'a\nb\nc', FAIL, b'a\nb\nc\n'),
|
||||
(
|
||||
b'#comment1\nfoo\n#comment2\nbar\n',
|
||||
FAIL,
|
||||
b'#comment2\nbar\n#comment1\nfoo\n',
|
||||
),
|
||||
(
|
||||
b'#comment1\nbar\n#comment2\nfoo\n',
|
||||
PASS,
|
||||
b'#comment1\nbar\n#comment2\nfoo\n',
|
||||
),
|
||||
(b'#comment\n\nfoo\nbar\n', FAIL, b'#comment\n\nbar\nfoo\n'),
|
||||
(b'#comment\n\nbar\nfoo\n', PASS, b'#comment\n\nbar\nfoo\n'),
|
||||
(
|
||||
b'foo\n\t#comment with indent\nbar\n',
|
||||
FAIL,
|
||||
b'\t#comment with indent\nbar\nfoo\n',
|
||||
),
|
||||
(
|
||||
b'bar\n\t#comment with indent\nfoo\n',
|
||||
PASS,
|
||||
b'bar\n\t#comment with indent\nfoo\n',
|
||||
),
|
||||
(b'\nfoo\nbar\n', FAIL, b'bar\n\nfoo\n'),
|
||||
(b'\nbar\nfoo\n', PASS, b'\nbar\nfoo\n'),
|
||||
(
|
||||
b'pyramid-foo==1\npyramid>=2\n',
|
||||
FAIL,
|
||||
b'pyramid>=2\npyramid-foo==1\n',
|
||||
),
|
||||
(
|
||||
b'a==1\n'
|
||||
b'c>=1\n'
|
||||
b'bbbb!=1\n'
|
||||
b'c-a>=1;python_version>="3.6"\n'
|
||||
b'e>=2\n'
|
||||
b'd>2\n'
|
||||
b'g<2\n'
|
||||
b'f<=2\n',
|
||||
FAIL,
|
||||
b'a==1\n'
|
||||
b'bbbb!=1\n'
|
||||
b'c>=1\n'
|
||||
b'c-a>=1;python_version>="3.6"\n'
|
||||
b'd>2\n'
|
||||
b'e>=2\n'
|
||||
b'f<=2\n'
|
||||
b'g<2\n',
|
||||
),
|
||||
(b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'),
|
||||
(
|
||||
b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n',
|
||||
FAIL,
|
||||
b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n',
|
||||
),
|
||||
(b'bar\npkg-resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'),
|
||||
(b'foo\npkg-resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'),
|
||||
(
|
||||
b'git+ssh://git_url@tag#egg=ocflib\nDjango\nijk\n',
|
||||
FAIL,
|
||||
b'Django\nijk\ngit+ssh://git_url@tag#egg=ocflib\n',
|
||||
),
|
||||
(
|
||||
b'b==1.0.0\n'
|
||||
b'c=2.0.0 \\\n'
|
||||
b' --hash=sha256:abcd\n'
|
||||
b'a=3.0.0 \\\n'
|
||||
b' --hash=sha256:a1b1c1d1',
|
||||
FAIL,
|
||||
b'a=3.0.0 \\\n'
|
||||
b' --hash=sha256:a1b1c1d1\n'
|
||||
b'b==1.0.0\n'
|
||||
b'c=2.0.0 \\\n'
|
||||
b' --hash=sha256:abcd\n',
|
||||
),
|
||||
(
|
||||
b'a=2.0.0 \\\n --hash=sha256:abcd\nb==1.0.0\n',
|
||||
PASS,
|
||||
b'a=2.0.0 \\\n --hash=sha256:abcd\nb==1.0.0\n',
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_integration(input_s, expected_retval, output, tmpdir):
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write_binary(input_s)
|
||||
|
||||
output_retval = main([str(path)])
|
||||
|
||||
assert path.read_binary() == output
|
||||
assert output_retval == expected_retval
|
||||
|
||||
|
||||
def test_requirement_object():
|
||||
top_of_file = Requirement()
|
||||
top_of_file.comments.append(b'#foo')
|
||||
top_of_file.value = b'\n'
|
||||
|
||||
requirement_foo = Requirement()
|
||||
requirement_foo.value = b'foo'
|
||||
|
||||
requirement_bar = Requirement()
|
||||
requirement_bar.value = b'bar'
|
||||
|
||||
# This may look redundant, but we need to test both foo.__lt__(bar) and
|
||||
# bar.__lt__(foo)
|
||||
assert requirement_foo > top_of_file
|
||||
assert top_of_file < requirement_foo
|
||||
assert requirement_foo > requirement_bar
|
||||
assert requirement_bar < requirement_foo
|
119
tests/sort_simple_yaml_test.py
Normal file
119
tests/sort_simple_yaml_test.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.sort_simple_yaml import first_key
|
||||
from pre_commit_hooks.sort_simple_yaml import main
|
||||
from pre_commit_hooks.sort_simple_yaml import parse_block
|
||||
from pre_commit_hooks.sort_simple_yaml import parse_blocks
|
||||
from pre_commit_hooks.sort_simple_yaml import sort
|
||||
|
||||
RETVAL_GOOD = 0
|
||||
RETVAL_BAD = 1
|
||||
TEST_SORTS = [
|
||||
(
|
||||
['c: true', '', 'b: 42', 'a: 19'],
|
||||
['b: 42', 'a: 19', '', 'c: true'],
|
||||
RETVAL_BAD,
|
||||
),
|
||||
|
||||
(
|
||||
['# i am', '# a header', '', 'c: true', '', 'b: 42', 'a: 19'],
|
||||
['# i am', '# a header', '', 'b: 42', 'a: 19', '', 'c: true'],
|
||||
RETVAL_BAD,
|
||||
),
|
||||
|
||||
(
|
||||
['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
|
||||
['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
|
||||
RETVAL_GOOD,
|
||||
),
|
||||
|
||||
(
|
||||
['# i am', '# a header'],
|
||||
['# i am', '# a header'],
|
||||
RETVAL_GOOD,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('bad_lines,good_lines,retval', TEST_SORTS)
|
||||
def test_integration_good_bad_lines(tmpdir, bad_lines, good_lines, retval):
|
||||
file_path = os.path.join(str(tmpdir), 'foo.yaml')
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
f.write('\n'.join(bad_lines) + '\n')
|
||||
|
||||
assert main([file_path]) == retval
|
||||
|
||||
with open(file_path) as f:
|
||||
assert [line.rstrip() for line in f.readlines()] == good_lines
|
||||
|
||||
|
||||
def test_parse_header():
|
||||
lines = ['# some header', '# is here', '', 'this is not a header']
|
||||
assert parse_block(lines, header=True) == ['# some header', '# is here']
|
||||
assert lines == ['', 'this is not a header']
|
||||
|
||||
lines = ['this is not a header']
|
||||
assert parse_block(lines, header=True) == []
|
||||
assert lines == ['this is not a header']
|
||||
|
||||
|
||||
def test_parse_block():
|
||||
# a normal block
|
||||
lines = ['a: 42', 'b: 17', '', 'c: 19']
|
||||
assert parse_block(lines) == ['a: 42', 'b: 17']
|
||||
assert lines == ['', 'c: 19']
|
||||
|
||||
# a block at the end
|
||||
lines = ['c: 19']
|
||||
assert parse_block(lines) == ['c: 19']
|
||||
assert lines == []
|
||||
|
||||
# no block
|
||||
lines = []
|
||||
assert parse_block(lines) == []
|
||||
assert lines == []
|
||||
|
||||
|
||||
def test_parse_blocks():
|
||||
# normal blocks
|
||||
lines = ['a: 42', 'b: 17', '', 'c: 19']
|
||||
assert parse_blocks(lines) == [['a: 42', 'b: 17'], ['c: 19']]
|
||||
assert lines == []
|
||||
|
||||
# a single block
|
||||
lines = ['a: 42', 'b: 17']
|
||||
assert parse_blocks(lines) == [['a: 42', 'b: 17']]
|
||||
assert lines == []
|
||||
|
||||
# no blocks
|
||||
lines = []
|
||||
assert parse_blocks(lines) == []
|
||||
assert lines == []
|
||||
|
||||
|
||||
def test_first_key():
|
||||
# first line
|
||||
lines = ['a: 42', 'b: 17', '', 'c: 19']
|
||||
assert first_key(lines) == 'a: 42'
|
||||
|
||||
# second line
|
||||
lines = ['# some comment', 'a: 42', 'b: 17', '', 'c: 19']
|
||||
assert first_key(lines) == 'a: 42'
|
||||
|
||||
# second line with quotes
|
||||
lines = ['# some comment', '"a": 42', 'b: 17', '', 'c: 19']
|
||||
assert first_key(lines) == 'a": 42'
|
||||
|
||||
# no lines (not a real situation)
|
||||
lines = []
|
||||
assert first_key(lines) == ''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('bad_lines,good_lines,_', TEST_SORTS)
|
||||
def test_sort(bad_lines, good_lines, _):
|
||||
assert sort(bad_lines) == good_lines
|
62
tests/string_fixer_test.py
Normal file
62
tests/string_fixer_test.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.string_fixer import main
|
||||
|
||||
TESTS = (
|
||||
# Base cases
|
||||
("''", "''", 0),
|
||||
('""', "''", 1),
|
||||
(r'"\'"', r'"\'"', 0),
|
||||
(r'"\""', r'"\""', 0),
|
||||
(r"'\"\"'", r"'\"\"'", 0),
|
||||
# String somewhere in the line
|
||||
('x = "foo"', "x = 'foo'", 1),
|
||||
# Test escaped characters
|
||||
(r'"\'"', r'"\'"', 0),
|
||||
# Docstring
|
||||
('""" Foo """', '""" Foo """', 0),
|
||||
(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
x = " \\
|
||||
foo \\
|
||||
"\n
|
||||
""",
|
||||
),
|
||||
textwrap.dedent(
|
||||
"""
|
||||
x = ' \\
|
||||
foo \\
|
||||
'\n
|
||||
""",
|
||||
),
|
||||
1,
|
||||
),
|
||||
('"foo""bar"', "'foo''bar'", 1),
|
||||
pytest.param(
|
||||
"f'hello{\"world\"}'",
|
||||
"f'hello{\"world\"}'",
|
||||
0,
|
||||
id='ignore nested fstrings',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('input_s', 'output', 'expected_retval'), TESTS)
|
||||
def test_rewrite(input_s, output, expected_retval, tmpdir):
|
||||
path = tmpdir.join('file.py')
|
||||
path.write(input_s)
|
||||
retval = main([str(path)])
|
||||
assert path.read() == output
|
||||
assert retval == expected_retval
|
||||
|
||||
|
||||
def test_rewrite_crlf(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write_binary(b'"foo"\r\n"bar"\r\n')
|
||||
assert main((str(f),))
|
||||
assert f.read_binary() == b"'foo'\r\n'bar'\r\n"
|
50
tests/tests_should_end_in_test_test.py
Normal file
50
tests/tests_should_end_in_test_test.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks.tests_should_end_in_test import main
|
||||
|
||||
|
||||
def test_main_all_pass():
|
||||
ret = main(['foo_test.py', 'bar_test.py'])
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_main_one_fails():
|
||||
ret = main(['not_test_ending.py', 'foo_test.py'])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_regex():
|
||||
assert main(('foo_test_py',)) == 1
|
||||
|
||||
|
||||
def test_main_django_all_pass():
|
||||
ret = main((
|
||||
'--django', 'tests.py', 'test_foo.py', 'test_bar.py',
|
||||
'tests/test_baz.py',
|
||||
))
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_main_django_one_fails():
|
||||
ret = main(['--django', 'not_test_ending.py', 'test_foo.py'])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_validate_nested_files_django_one_fails():
|
||||
ret = main(['--django', 'tests/not_test_ending.py', 'test_foo.py'])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_main_not_django_fails():
|
||||
ret = main(['foo_test.py', 'bar_test.py', 'test_baz.py'])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_main_django_fails():
|
||||
ret = main(['--django', 'foo_test.py', 'test_bar.py', 'test_baz.py'])
|
||||
assert ret == 1
|
||||
|
||||
|
||||
def test_main_pytest_test_first():
|
||||
assert main(['--pytest-test-first', 'test_foo.py']) == 0
|
||||
assert main(['--pytest-test-first', 'foo_test.py']) == 1
|
103
tests/trailing_whitespace_fixer_test.py
Normal file
103
tests/trailing_whitespace_fixer_test.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.trailing_whitespace_fixer import main
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'expected'),
|
||||
(
|
||||
('foo \nbar \n', 'foo\nbar\n'),
|
||||
('bar\t\nbaz\t\n', 'bar\nbaz\n'),
|
||||
),
|
||||
)
|
||||
def test_fixes_trailing_whitespace(input_s, expected, tmpdir):
|
||||
path = tmpdir.join('file.md')
|
||||
path.write(input_s)
|
||||
assert main((str(path),)) == 1
|
||||
assert path.read() == expected
|
||||
|
||||
|
||||
def test_ok_no_newline_end_of_file(tmpdir):
|
||||
filename = tmpdir.join('f')
|
||||
filename.write_binary(b'foo\nbar')
|
||||
ret = main((str(filename),))
|
||||
assert filename.read_binary() == b'foo\nbar'
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_ok_with_dos_line_endings(tmpdir):
|
||||
filename = tmpdir.join('f')
|
||||
filename.write_binary(b'foo\r\nbar\r\nbaz\r\n')
|
||||
ret = main((str(filename),))
|
||||
assert filename.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
|
||||
assert ret == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ext', ('md', 'Md', '.md', '*'))
|
||||
def test_fixes_markdown_files(tmpdir, ext):
|
||||
path = tmpdir.join('test.md')
|
||||
path.write(
|
||||
'foo \n' # leaves alone
|
||||
'bar \n' # less than two so it is removed
|
||||
'baz \n' # more than two so it becomes two spaces
|
||||
'\t\n' # trailing tabs are stripped anyway
|
||||
'\n ', # whitespace at the end of the file is removed
|
||||
)
|
||||
ret = main((str(path), f'--markdown-linebreak-ext={ext}'))
|
||||
assert ret == 1
|
||||
assert path.read() == (
|
||||
'foo \n'
|
||||
'bar\n'
|
||||
'baz \n'
|
||||
'\n'
|
||||
'\n'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('arg', ('--', 'a.b', 'a/b', ''))
|
||||
def test_markdown_linebreak_ext_badopt(arg):
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
main(['--markdown-linebreak-ext', arg])
|
||||
assert excinfo.value.code == 2
|
||||
|
||||
|
||||
def test_prints_warning_with_no_markdown_ext(capsys, tmpdir):
|
||||
f = tmpdir.join('f').ensure()
|
||||
assert main((str(f), '--no-markdown-linebreak-ext')) == 0
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == '--no-markdown-linebreak-ext now does nothing!\n'
|
||||
|
||||
|
||||
def test_preserve_non_utf8_file(tmpdir):
|
||||
non_utf8_bytes_content = b'<a>\xe9 \n</a>\n'
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write_binary(non_utf8_bytes_content)
|
||||
ret = main([str(path)])
|
||||
assert ret == 1
|
||||
assert path.size() == (len(non_utf8_bytes_content) - 1)
|
||||
|
||||
|
||||
def test_custom_charset_change(tmpdir):
|
||||
# strip spaces only, no tabs
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write('\ta \t \n')
|
||||
ret = main([str(path), '--chars', ' '])
|
||||
assert ret == 1
|
||||
assert path.read() == '\ta \t\n'
|
||||
|
||||
|
||||
def test_custom_charset_no_change(tmpdir):
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write('\ta \t\n')
|
||||
ret = main([str(path), '--chars', ' '])
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_markdown_with_custom_charset(tmpdir):
|
||||
path = tmpdir.join('file.md')
|
||||
path.write('\ta \t \n')
|
||||
ret = main([str(path), '--chars', ' ', '--markdown-linebreak-ext', '*'])
|
||||
assert ret == 1
|
||||
assert path.read() == '\ta \t \n'
|
27
tests/util_test.py
Normal file
27
tests/util_test.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.util import CalledProcessError
|
||||
from pre_commit_hooks.util import cmd_output
|
||||
from pre_commit_hooks.util import zsplit
|
||||
|
||||
|
||||
def test_raises_on_error():
|
||||
with pytest.raises(CalledProcessError):
|
||||
cmd_output('sh', '-c', 'exit 1')
|
||||
|
||||
|
||||
def test_output():
|
||||
ret = cmd_output('sh', '-c', 'echo hi')
|
||||
assert ret == 'hi\n'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('out', ('\0f1\0f2\0', '\0f1\0f2', 'f1\0f2\0'))
|
||||
def test_check_zsplits_str_correctly(out):
|
||||
assert zsplit(out) == ['f1', 'f2']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('out', ('\0\0', '\0', ''))
|
||||
def test_check_zsplit_returns_empty(out):
|
||||
assert zsplit(out) == []
|
Loading…
Add table
Add a link
Reference in a new issue