1
0
Fork 0

Adding upstream version 4.5.0+dfsg.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 20:21:14 +01:00
parent 27cd5628db
commit 6bd375ed5f
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
108 changed files with 6514 additions and 0 deletions

0
tests/__init__.py Normal file
View file

View 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
View 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

View 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

View 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

View 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

View 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

View 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
View 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),))

View 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)

View 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

View 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
View 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

View 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
View 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
View 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
View 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

View 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'

View 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

View 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

View 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

View 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

View 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

View 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

View 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'

View 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

View 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'

View 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

View 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
View 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
View 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'
)

View 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

View 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

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

View 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

View 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
View 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) == []