diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:05:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:05:20 +0000 |
commit | c86df75ab11643fa4649cfe6ed5c4692d4ee342b (patch) | |
tree | de847f47ec2669e74b9a3459319579346b7c99df /tests/commands/hook_impl_test.py | |
parent | Initial commit. (diff) | |
download | pre-commit-c86df75ab11643fa4649cfe6ed5c4692d4ee342b.tar.xz pre-commit-c86df75ab11643fa4649cfe6ed5c4692d4ee342b.zip |
Adding upstream version 3.6.2.upstream/3.6.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/commands/hook_impl_test.py')
-rw-r--r-- | tests/commands/hook_impl_test.py | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py new file mode 100644 index 0000000..d757e85 --- /dev/null +++ b/tests/commands/hook_impl_test.py @@ -0,0 +1,381 @@ +from __future__ import annotations + +import subprocess +import sys +from unittest import mock + +import pytest + +import pre_commit.constants as C +from pre_commit import git +from pre_commit.commands import hook_impl +from pre_commit.envcontext import envcontext +from pre_commit.util import cmd_output +from pre_commit.util import make_executable +from testing.fixtures import git_dir +from testing.fixtures import sample_local_config +from testing.fixtures import write_config +from testing.util import cwd +from testing.util import git_commit + + +def test_validate_config_file_exists(tmpdir): + cfg = tmpdir.join(C.CONFIG_FILE).ensure() + hook_impl._validate_config(0, cfg, True) + + +def test_validate_config_missing(capsys): + with pytest.raises(SystemExit) as excinfo: + hook_impl._validate_config(123, 'DNE.yaml', False) + ret, = excinfo.value.args + assert ret == 1 + assert capsys.readouterr().out == ( + 'No DNE.yaml file was found\n' + '- To temporarily silence this, run ' + '`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n' + '- To permanently silence this, install pre-commit with the ' + '--allow-missing-config option\n' + '- To uninstall pre-commit run `pre-commit uninstall`\n' + ) + + +def test_validate_config_skip_missing_config(capsys): + with pytest.raises(SystemExit) as excinfo: + hook_impl._validate_config(123, 'DNE.yaml', True) + ret, = excinfo.value.args + assert ret == 123 + expected = '`DNE.yaml` config file not found. Skipping `pre-commit`.\n' + assert capsys.readouterr().out == expected + + +def test_validate_config_skip_via_env_variable(capsys): + with pytest.raises(SystemExit) as excinfo: + with envcontext((('PRE_COMMIT_ALLOW_NO_CONFIG', '1'),)): + hook_impl._validate_config(0, 'DNE.yaml', False) + ret, = excinfo.value.args + assert ret == 0 + expected = '`DNE.yaml` config file not found. Skipping `pre-commit`.\n' + assert capsys.readouterr().out == expected + + +def test_run_legacy_does_not_exist(tmpdir): + retv, stdin = hook_impl._run_legacy('pre-commit', tmpdir, ()) + assert (retv, stdin) == (0, b'') + + +def test_run_legacy_executes_legacy_script(tmpdir, capfd): + hook = tmpdir.join('pre-commit.legacy') + hook.write('#!/usr/bin/env bash\necho hi "$@"\nexit 1\n') + make_executable(hook) + retv, stdin = hook_impl._run_legacy('pre-commit', tmpdir, ('arg1', 'arg2')) + assert capfd.readouterr().out.strip() == 'hi arg1 arg2' + assert (retv, stdin) == (1, b'') + + +def test_run_legacy_pre_push_returns_stdin(tmpdir): + with mock.patch.object(sys.stdin.buffer, 'read', return_value=b'stdin'): + retv, stdin = hook_impl._run_legacy('pre-push', tmpdir, ()) + assert (retv, stdin) == (0, b'stdin') + + +def test_run_legacy_recursive(tmpdir): + hook = tmpdir.join('pre-commit.legacy').ensure() + make_executable(hook) + + # simulate a call being recursive + def call(*_, **__): + return hook_impl._run_legacy('pre-commit', tmpdir, ()) + + with mock.patch.object(subprocess, 'run', call): + with pytest.raises(SystemExit): + call() + + +@pytest.mark.parametrize( + ('hook_type', 'args'), + ( + ('pre-commit', []), + ('pre-merge-commit', []), + ('pre-push', ['branch_name', 'remote_name']), + ('commit-msg', ['.git/COMMIT_EDITMSG']), + ('post-commit', []), + ('post-merge', ['1']), + ('pre-rebase', ['main', 'topic']), + ('pre-rebase', ['main']), + ('post-checkout', ['old_head', 'new_head', '1']), + ('post-rewrite', ['amend']), + # multiple choices for commit-editmsg + ('prepare-commit-msg', ['.git/COMMIT_EDITMSG']), + ('prepare-commit-msg', ['.git/COMMIT_EDITMSG', 'message']), + ('prepare-commit-msg', ['.git/COMMIT_EDITMSG', 'commit', 'deadbeef']), + ), +) +def test_check_args_length_ok(hook_type, args): + hook_impl._check_args_length(hook_type, args) + + +def test_check_args_length_error_too_many_plural(): + with pytest.raises(SystemExit) as excinfo: + hook_impl._check_args_length('pre-commit', ['run', '--all-files']) + msg, = excinfo.value.args + assert msg == ( + 'hook-impl for pre-commit expected 0 arguments but got 2: ' + "['run', '--all-files']" + ) + + +def test_check_args_length_error_too_many_singular(): + with pytest.raises(SystemExit) as excinfo: + hook_impl._check_args_length('commit-msg', []) + msg, = excinfo.value.args + assert msg == 'hook-impl for commit-msg expected 1 argument but got 0: []' + + +def test_check_args_length_prepare_commit_msg_error(): + with pytest.raises(SystemExit) as excinfo: + hook_impl._check_args_length('prepare-commit-msg', []) + msg, = excinfo.value.args + assert msg == ( + 'hook-impl for prepare-commit-msg expected 1, 2, or 3 arguments ' + 'but got 0: []' + ) + + +def test_check_args_length_pre_rebase_error(): + with pytest.raises(SystemExit) as excinfo: + hook_impl._check_args_length('pre-rebase', []) + msg, = excinfo.value.args + assert msg == 'hook-impl for pre-rebase expected 1 or 2 arguments but got 0: []' # noqa: E501 + + +def test_run_ns_pre_commit(): + ns = hook_impl._run_ns('pre-commit', True, (), b'') + assert ns is not None + assert ns.hook_stage == 'pre-commit' + assert ns.color is True + + +def test_run_ns_pre_rebase(): + ns = hook_impl._run_ns('pre-rebase', True, ('main', 'topic'), b'') + assert ns is not None + assert ns.hook_stage == 'pre-rebase' + assert ns.color is True + assert ns.pre_rebase_upstream == 'main' + assert ns.pre_rebase_branch == 'topic' + + ns = hook_impl._run_ns('pre-rebase', True, ('main',), b'') + assert ns is not None + assert ns.hook_stage == 'pre-rebase' + assert ns.color is True + assert ns.pre_rebase_upstream == 'main' + assert ns.pre_rebase_branch is None + + +def test_run_ns_commit_msg(): + ns = hook_impl._run_ns('commit-msg', False, ('.git/COMMIT_MSG',), b'') + assert ns is not None + assert ns.hook_stage == 'commit-msg' + assert ns.color is False + assert ns.commit_msg_filename == '.git/COMMIT_MSG' + + +def test_run_ns_prepare_commit_msg_one_arg(): + ns = hook_impl._run_ns( + 'prepare-commit-msg', False, + ('.git/COMMIT_MSG',), b'', + ) + assert ns is not None + assert ns.hook_stage == 'prepare-commit-msg' + assert ns.color is False + assert ns.commit_msg_filename == '.git/COMMIT_MSG' + + +def test_run_ns_prepare_commit_msg_two_arg(): + ns = hook_impl._run_ns( + 'prepare-commit-msg', False, + ('.git/COMMIT_MSG', 'message'), b'', + ) + assert ns is not None + assert ns.hook_stage == 'prepare-commit-msg' + assert ns.color is False + assert ns.commit_msg_filename == '.git/COMMIT_MSG' + assert ns.prepare_commit_message_source == 'message' + + +def test_run_ns_prepare_commit_msg_three_arg(): + ns = hook_impl._run_ns( + 'prepare-commit-msg', False, + ('.git/COMMIT_MSG', 'message', 'HEAD'), b'', + ) + assert ns is not None + assert ns.hook_stage == 'prepare-commit-msg' + assert ns.color is False + assert ns.commit_msg_filename == '.git/COMMIT_MSG' + assert ns.prepare_commit_message_source == 'message' + assert ns.commit_object_name == 'HEAD' + + +def test_run_ns_post_commit(): + ns = hook_impl._run_ns('post-commit', True, (), b'') + assert ns is not None + assert ns.hook_stage == 'post-commit' + assert ns.color is True + + +def test_run_ns_post_merge(): + ns = hook_impl._run_ns('post-merge', True, ('1',), b'') + assert ns is not None + assert ns.hook_stage == 'post-merge' + assert ns.color is True + assert ns.is_squash_merge == '1' + + +def test_run_ns_post_rewrite(): + ns = hook_impl._run_ns('post-rewrite', True, ('amend',), b'') + assert ns is not None + assert ns.hook_stage == 'post-rewrite' + assert ns.color is True + assert ns.rewrite_command == 'amend' + + +def test_run_ns_post_checkout(): + ns = hook_impl._run_ns('post-checkout', True, ('a', 'b', 'c'), b'') + assert ns is not None + assert ns.hook_stage == 'post-checkout' + assert ns.color is True + assert ns.from_ref == 'a' + assert ns.to_ref == 'b' + assert ns.checkout_type == 'c' + + +@pytest.fixture +def push_example(tempdir_factory): + src = git_dir(tempdir_factory) + git_commit(cwd=src) + src_head = git.head_rev(src) + + clone = tempdir_factory.get() + cmd_output('git', 'clone', src, clone) + git_commit(cwd=clone) + clone_head = git.head_rev(clone) + return (src, src_head, clone, clone_head) + + +def test_run_ns_pre_push_updating_branch(push_example): + src, src_head, clone, clone_head = push_example + + with cwd(clone): + args = ('origin', src) + stdin = f'HEAD {clone_head} refs/heads/b {src_head}\n'.encode() + ns = hook_impl._run_ns('pre-push', False, args, stdin) + + assert ns is not None + assert ns.hook_stage == 'pre-push' + assert ns.color is False + assert ns.remote_name == 'origin' + assert ns.remote_url == src + assert ns.from_ref == src_head + assert ns.to_ref == clone_head + assert ns.all_files is False + + +def test_run_ns_pre_push_new_branch(push_example): + src, src_head, clone, clone_head = push_example + + with cwd(clone): + args = ('origin', src) + stdin = f'HEAD {clone_head} refs/heads/b {hook_impl.Z40}\n'.encode() + ns = hook_impl._run_ns('pre-push', False, args, stdin) + + assert ns is not None + assert ns.from_ref == src_head + assert ns.to_ref == clone_head + + +def test_run_ns_pre_push_new_branch_existing_rev(push_example): + src, src_head, clone, _ = push_example + + with cwd(clone): + args = ('origin', src) + stdin = f'HEAD {src_head} refs/heads/b2 {hook_impl.Z40}\n'.encode() + ns = hook_impl._run_ns('pre-push', False, args, stdin) + + assert ns is None + + +def test_run_ns_pre_push_ref_with_whitespace(push_example): + src, src_head, clone, _ = push_example + + with cwd(clone): + args = ('origin', src) + line = f'HEAD^{{/ }} {src_head} refs/heads/b2 {hook_impl.Z40}\n' + stdin = line.encode() + ns = hook_impl._run_ns('pre-push', False, args, stdin) + + assert ns is None + + +def test_pushing_orphan_branch(push_example): + src, src_head, clone, _ = push_example + + cmd_output('git', 'checkout', '--orphan', 'b2', cwd=clone) + git_commit(cwd=clone, msg='something else to get unique hash') + clone_rev = git.head_rev(clone) + + with cwd(clone): + args = ('origin', src) + stdin = f'HEAD {clone_rev} refs/heads/b2 {hook_impl.Z40}\n'.encode() + ns = hook_impl._run_ns('pre-push', False, args, stdin) + + assert ns is not None + assert ns.all_files is True + + +def test_run_ns_pre_push_deleting_branch(push_example): + src, src_head, clone, _ = push_example + + with cwd(clone): + args = ('origin', src) + stdin = f'(delete) {hook_impl.Z40} refs/heads/b {src_head}'.encode() + ns = hook_impl._run_ns('pre-push', False, args, stdin) + + assert ns is None + + +def test_hook_impl_main_noop_pre_push(cap_out, store, push_example): + src, src_head, clone, _ = push_example + + stdin = f'(delete) {hook_impl.Z40} refs/heads/b {src_head}'.encode() + with mock.patch.object(sys.stdin.buffer, 'read', return_value=stdin): + with cwd(clone): + write_config('.', sample_local_config()) + ret = hook_impl.hook_impl( + store, + config=C.CONFIG_FILE, + color=False, + hook_type='pre-push', + hook_dir='.git/hooks', + skip_on_missing_config=False, + args=('origin', src), + ) + assert ret == 0 + assert cap_out.get() == '' + + +def test_hook_impl_main_runs_hooks(cap_out, tempdir_factory, store): + with cwd(git_dir(tempdir_factory)): + write_config('.', sample_local_config()) + ret = hook_impl.hook_impl( + store, + config=C.CONFIG_FILE, + color=False, + hook_type='pre-commit', + hook_dir='.git/hooks', + skip_on_missing_config=False, + args=(), + ) + assert ret == 0 + expected = '''\ +Block if "DO NOT COMMIT" is found....................(no files to check)Skipped +''' + assert cap_out.get() == expected |