summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/all_languages_test.py7
-rw-r--r--tests/clientlib_test.py68
-rw-r--r--tests/commands/migrate_config_test.py104
-rw-r--r--tests/conftest.py7
-rw-r--r--tests/repository_test.py26
-rw-r--r--tests/store_test.py77
-rw-r--r--tests/yaml_rewrite_test.py47
7 files changed, 296 insertions, 40 deletions
diff --git a/tests/all_languages_test.py b/tests/all_languages_test.py
deleted file mode 100644
index 98c9121..0000000
--- a/tests/all_languages_test.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from __future__ import annotations
-
-from pre_commit.all_languages import languages
-
-
-def test_python_venv_is_an_alias_to_python():
- assert languages['python_venv'] is languages['python']
diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py
index eaa8a04..7aa84af 100644
--- a/tests/clientlib_test.py
+++ b/tests/clientlib_test.py
@@ -256,6 +256,24 @@ def test_validate_optional_sensible_regex_at_local_hook(caplog):
]
+def test_validate_optional_sensible_regex_at_meta_hook(caplog):
+ config_obj = {
+ 'repo': 'meta',
+ 'hooks': [{'id': 'identity', 'files': 'dir/*.py'}],
+ }
+
+ cfgv.validate(config_obj, CONFIG_REPO_DICT)
+
+ assert caplog.record_tuples == [
+ (
+ 'pre_commit',
+ logging.WARNING,
+ "The 'files' field in hook 'identity' is a regex, not a glob "
+ "-- matching '/*' probably isn't what you want here",
+ ),
+ ]
+
+
@pytest.mark.parametrize(
('regex', 'warning'),
(
@@ -291,6 +309,56 @@ def test_validate_optional_sensible_regex_at_top_level(caplog, regex, warning):
assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)]
+def test_warning_for_deprecated_stages(caplog):
+ config_obj = sample_local_config()
+ config_obj['hooks'][0]['stages'] = ['commit', 'push']
+
+ cfgv.validate(config_obj, CONFIG_REPO_DICT)
+
+ assert caplog.record_tuples == [
+ (
+ 'pre_commit',
+ logging.WARNING,
+ 'hook id `do_not_commit` uses deprecated stage names '
+ '(commit, push) which will be removed in a future version. '
+ 'run: `pre-commit migrate-config` to automatically fix this.',
+ ),
+ ]
+
+
+def test_no_warning_for_non_deprecated_stages(caplog):
+ config_obj = sample_local_config()
+ config_obj['hooks'][0]['stages'] = ['pre-commit', 'pre-push']
+
+ cfgv.validate(config_obj, CONFIG_REPO_DICT)
+
+ assert caplog.record_tuples == []
+
+
+def test_warning_for_deprecated_default_stages(caplog):
+ cfg = {'default_stages': ['commit', 'push'], 'repos': []}
+
+ cfgv.validate(cfg, CONFIG_SCHEMA)
+
+ assert caplog.record_tuples == [
+ (
+ 'pre_commit',
+ logging.WARNING,
+ 'top-level `default_stages` uses deprecated stage names '
+ '(commit, push) which will be removed in a future version. '
+ 'run: `pre-commit migrate-config` to automatically fix this.',
+ ),
+ ]
+
+
+def test_no_warning_for_non_deprecated_default_stages(caplog):
+ cfg = {'default_stages': ['pre-commit', 'pre-push'], 'repos': []}
+
+ cfgv.validate(cfg, CONFIG_SCHEMA)
+
+ assert caplog.record_tuples == []
+
+
@pytest.mark.parametrize(
'manifest_obj',
(
diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py
index ba18463..a517d2f 100644
--- a/tests/commands/migrate_config_test.py
+++ b/tests/commands/migrate_config_test.py
@@ -1,10 +1,26 @@
from __future__ import annotations
+from unittest import mock
+
import pytest
+import yaml
import pre_commit.constants as C
from pre_commit.clientlib import InvalidConfigError
from pre_commit.commands.migrate_config import migrate_config
+from pre_commit.yaml import yaml_compose
+
+
+@pytest.fixture(autouse=True, params=['c', 'pure'])
+def switch_pyyaml_impl(request):
+ if request.param == 'c':
+ yield
+ else:
+ with mock.patch.dict(
+ yaml_compose.keywords,
+ {'Loader': yaml.SafeLoader},
+ ):
+ yield
def test_migrate_config_normal_format(tmpdir, capsys):
@@ -134,6 +150,27 @@ def test_migrate_config_sha_to_rev(tmpdir):
)
+def test_migrate_config_sha_to_rev_json(tmp_path):
+ contents = """\
+{"repos": [{
+ "repo": "https://github.com/pre-commit/pre-commit-hooks",
+ "sha": "v1.2.0",
+ "hooks": []
+}]}
+"""
+ expected = """\
+{"repos": [{
+ "repo": "https://github.com/pre-commit/pre-commit-hooks",
+ "rev": "v1.2.0",
+ "hooks": []
+}]}
+"""
+ cfg = tmp_path.joinpath('cfg.yaml')
+ cfg.write_text(contents)
+ assert not migrate_config(str(cfg))
+ assert cfg.read_text() == expected
+
+
def test_migrate_config_language_python_venv(tmp_path):
src = '''\
repos:
@@ -167,6 +204,73 @@ repos:
assert cfg.read_text() == expected
+def test_migrate_config_quoted_python_venv(tmp_path):
+ src = '''\
+repos:
+- repo: local
+ hooks:
+ - id: example
+ name: example
+ entry: example
+ language: "python_venv"
+'''
+ expected = '''\
+repos:
+- repo: local
+ hooks:
+ - id: example
+ name: example
+ entry: example
+ language: "python"
+'''
+ cfg = tmp_path.joinpath('cfg.yaml')
+ cfg.write_text(src)
+ assert migrate_config(str(cfg)) == 0
+ assert cfg.read_text() == expected
+
+
+def test_migrate_config_default_stages(tmp_path):
+ src = '''\
+default_stages: [commit, push, merge-commit, commit-msg]
+repos: []
+'''
+ expected = '''\
+default_stages: [pre-commit, pre-push, pre-merge-commit, commit-msg]
+repos: []
+'''
+ cfg = tmp_path.joinpath('cfg.yaml')
+ cfg.write_text(src)
+ assert migrate_config(str(cfg)) == 0
+ assert cfg.read_text() == expected
+
+
+def test_migrate_config_hook_stages(tmp_path):
+ src = '''\
+repos:
+- repo: local
+ hooks:
+ - id: example
+ name: example
+ entry: example
+ language: system
+ stages: ["commit", "push", "merge-commit", "commit-msg"]
+'''
+ expected = '''\
+repos:
+- repo: local
+ hooks:
+ - id: example
+ name: example
+ entry: example
+ language: system
+ stages: ["pre-commit", "pre-push", "pre-merge-commit", "commit-msg"]
+'''
+ cfg = tmp_path.joinpath('cfg.yaml')
+ cfg.write_text(src)
+ assert migrate_config(str(cfg)) == 0
+ assert cfg.read_text() == expected
+
+
def test_migrate_config_invalid_yaml(tmpdir):
contents = '['
cfg = tmpdir.join(C.CONFIG_FILE)
diff --git a/tests/conftest.py b/tests/conftest.py
index bd4af9a..8c9cd14 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,7 +2,6 @@ from __future__ import annotations
import functools
import io
-import logging
import os.path
from unittest import mock
@@ -203,12 +202,6 @@ def store(tempdir_factory):
yield Store(os.path.join(tempdir_factory.get(), '.pre-commit'))
-@pytest.fixture
-def log_info_mock():
- with mock.patch.object(logging.getLogger('pre_commit'), 'info') as mck:
- yield mck
-
-
class Fixture:
def __init__(self, stream: io.BytesIO) -> None:
self._stream = stream
diff --git a/tests/repository_test.py b/tests/repository_test.py
index ac065ec..b54c910 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -80,24 +80,6 @@ def _test_hook_repo(
assert out == expected
-def test_python_venv_deprecation(store, caplog):
- config = {
- 'repo': 'local',
- 'hooks': [{
- 'id': 'example',
- 'name': 'example',
- 'language': 'python_venv',
- 'entry': 'echo hi',
- }],
- }
- _get_hook(config, store, 'example')
- assert caplog.messages[-1] == (
- '`repo: local` uses deprecated `language: python_venv`. '
- 'This is an alias for `language: python`. '
- 'Often `pre-commit autoupdate --repo local` will fix this.'
- )
-
-
def test_system_hook_with_spaces(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'system_hook_with_spaces_repo',
@@ -240,16 +222,16 @@ def test_unknown_keys(store, caplog):
assert msg == 'Unexpected key(s) present on local => too-much: foo, hello'
-def test_reinstall(tempdir_factory, store, log_info_mock):
+def test_reinstall(tempdir_factory, store, caplog):
path = make_repo(tempdir_factory, 'python_hooks_repo')
config = make_config_from_repo(path)
_get_hook(config, store, 'foo')
# We print some logging during clone (1) + install (3)
- assert log_info_mock.call_count == 4
- log_info_mock.reset_mock()
+ assert len(caplog.record_tuples) == 4
+ caplog.clear()
# Reinstall on another run should not trigger another install
_get_hook(config, store, 'foo')
- assert log_info_mock.call_count == 0
+ assert len(caplog.record_tuples) == 0
def test_control_c_control_c_on_install(tempdir_factory, store):
diff --git a/tests/store_test.py b/tests/store_test.py
index 45ec732..7d4dffb 100644
--- a/tests/store_test.py
+++ b/tests/store_test.py
@@ -1,12 +1,15 @@
from __future__ import annotations
+import logging
import os.path
+import shlex
import sqlite3
import stat
from unittest import mock
import pytest
+import pre_commit.constants as C
from pre_commit import git
from pre_commit.store import _get_default_directory
from pre_commit.store import _LOCAL_RESOURCES
@@ -65,7 +68,7 @@ def test_store_init(store):
assert text_line in readme_contents
-def test_clone(store, tempdir_factory, log_info_mock):
+def test_clone(store, tempdir_factory, caplog):
path = git_dir(tempdir_factory)
with cwd(path):
git_commit()
@@ -74,7 +77,7 @@ def test_clone(store, tempdir_factory, log_info_mock):
ret = store.clone(path, rev)
# Should have printed some stuff
- assert log_info_mock.call_args_list[0][0][0].startswith(
+ assert caplog.record_tuples[0][-1].startswith(
'Initializing environment for ',
)
@@ -91,6 +94,72 @@ def test_clone(store, tempdir_factory, log_info_mock):
assert store.select_all_repos() == [(path, rev, ret)]
+def test_warning_for_deprecated_stages_on_init(store, tempdir_factory, caplog):
+ manifest = '''\
+- id: hook1
+ name: hook1
+ language: system
+ entry: echo hook1
+ stages: [commit, push]
+- id: hook2
+ name: hook2
+ language: system
+ entry: echo hook2
+ stages: [push, merge-commit]
+'''
+
+ path = git_dir(tempdir_factory)
+ with open(os.path.join(path, C.MANIFEST_FILE), 'w') as f:
+ f.write(manifest)
+ cmd_output('git', 'add', '.', cwd=path)
+ git_commit(cwd=path)
+ rev = git.head_rev(path)
+
+ store.clone(path, rev)
+ assert caplog.record_tuples[1] == (
+ 'pre_commit',
+ logging.WARNING,
+ f'repo `{path}` uses deprecated stage names '
+ f'(commit, push, merge-commit) which will be removed in a future '
+ f'version. '
+ f'Hint: often `pre-commit autoupdate --repo {shlex.quote(path)}` '
+ f'will fix this. '
+ f'if it does not -- consider reporting an issue to that repo.',
+ )
+
+ # should not re-warn
+ caplog.clear()
+ store.clone(path, rev)
+ assert caplog.record_tuples == []
+
+
+def test_no_warning_for_non_deprecated_stages_on_init(
+ store, tempdir_factory, caplog,
+):
+ manifest = '''\
+- id: hook1
+ name: hook1
+ language: system
+ entry: echo hook1
+ stages: [pre-commit, pre-push]
+- id: hook2
+ name: hook2
+ language: system
+ entry: echo hook2
+ stages: [pre-push, pre-merge-commit]
+'''
+
+ path = git_dir(tempdir_factory)
+ with open(os.path.join(path, C.MANIFEST_FILE), 'w') as f:
+ f.write(manifest)
+ cmd_output('git', 'add', '.', cwd=path)
+ git_commit(cwd=path)
+ rev = git.head_rev(path)
+
+ store.clone(path, rev)
+ assert logging.WARNING not in {tup[1] for tup in caplog.record_tuples}
+
+
def test_clone_cleans_up_on_checkout_failure(store):
with pytest.raises(Exception) as excinfo:
# This raises an exception because you can't clone something that
@@ -118,7 +187,7 @@ def test_clone_when_repo_already_exists(store):
def test_clone_shallow_failure_fallback_to_complete(
store, tempdir_factory,
- log_info_mock,
+ caplog,
):
path = git_dir(tempdir_factory)
with cwd(path):
@@ -134,7 +203,7 @@ def test_clone_shallow_failure_fallback_to_complete(
ret = store.clone(path, rev)
# Should have printed some stuff
- assert log_info_mock.call_args_list[0][0][0].startswith(
+ assert caplog.record_tuples[0][-1].startswith(
'Initializing environment for ',
)
diff --git a/tests/yaml_rewrite_test.py b/tests/yaml_rewrite_test.py
new file mode 100644
index 0000000..d0f6841
--- /dev/null
+++ b/tests/yaml_rewrite_test.py
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+import pytest
+
+from pre_commit.yaml import yaml_compose
+from pre_commit.yaml_rewrite import MappingKey
+from pre_commit.yaml_rewrite import MappingValue
+from pre_commit.yaml_rewrite import match
+from pre_commit.yaml_rewrite import SequenceItem
+
+
+def test_match_produces_scalar_values_only():
+ src = '''\
+- name: foo
+- name: [not, foo] # not a scalar: should be skipped!
+- name: bar
+'''
+ matcher = (SequenceItem(), MappingValue('name'))
+ ret = [n.value for n in match(yaml_compose(src), matcher)]
+ assert ret == ['foo', 'bar']
+
+
+@pytest.mark.parametrize('cls', (MappingKey, MappingValue))
+def test_mapping_not_a_map(cls):
+ m = cls('s')
+ assert list(m.match(yaml_compose('[foo]'))) == []
+
+
+def test_sequence_item_not_a_sequence():
+ assert list(SequenceItem().match(yaml_compose('s: val'))) == []
+
+
+def test_mapping_key():
+ m = MappingKey('s')
+ ret = [n.value for n in m.match(yaml_compose('s: val\nt: val2'))]
+ assert ret == ['s']
+
+
+def test_mapping_value():
+ m = MappingValue('s')
+ ret = [n.value for n in m.match(yaml_compose('s: val\nt: val2'))]
+ assert ret == ['val']
+
+
+def test_sequence_item():
+ ret = [n.value for n in SequenceItem().match(yaml_compose('[a, b, c]'))]
+ assert ret == ['a', 'b', 'c']