diff options
Diffstat (limited to '')
53 files changed, 392 insertions, 131 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 6cce5fe..bfced0f 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -9,6 +9,13 @@ body: [pre-commit.ci]: https://pre-commit.ci [pre-commit-ci/issues]: https://github.com/pre-commit-ci/issues + - type: input + id: search + attributes: + label: search tried in the issue tracker + placeholder: ... + validations: + required: true - type: textarea id: freeform attributes: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b93cff..7791f76 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -10,22 +10,22 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.20.0 + rev: v1.20.1 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py37-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.2.1 + rev: v2.2.3 hooks: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.31.1 + rev: v2.32.0 hooks: - id: pyupgrade args: [--py37-plus] @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.942 + rev: v0.950 hooks: - id: mypy additional_dependencies: [types-all] diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 3d1ffbc..e1aaf58 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,6 +1,6 @@ - id: validate_manifest name: validate pre-commit manifest description: This validator validates a pre-commit hooks manifest file - entry: pre-commit-validate-manifest + entry: pre-commit validate-manifest language: python - files: ^(\.pre-commit-hooks\.yaml|hooks\.yaml)$ + files: ^\.pre-commit-hooks\.yaml$ diff --git a/CHANGELOG.md b/CHANGELOG.md index cd31c4b..1b6d8b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +2.19.0 - 2022-05-05 +=================== + +### Features +- Allow multiple outputs from `language: dotnet` hooks. + - #2332 PR by @WallucePinkham. +- Add more information to `healthy()` failure. + - #2348 PR by @asottile. +- Upgrade ruby-build. + - #2342 PR by @jalessio. +- Add `pre-commit validate-config` / `pre-commit validate-manifest` and + deprecate `pre-commit-validate-config` and `pre-commit-validate-manifest`. + - #2362 PR by @asottile. + +### Fixes +- Fix `pre-push` when pushed ref contains spaces. + - #2345 PR by @wwade. + - #2344 issue by @wwade. + +### Updating +- Change `pre-commit-validate-config` / `pre-commit-validate-manifest` to + `pre-commit validate-config` / `pre-commit validate-manifest`. + - #2362 PR by @asottile. + 2.18.1 - 2022-04-02 =================== diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index adce08f..fa1678c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,15 +117,16 @@ get_default_version = helpers.basic_default_version `python` is currently the only language which implements this api -#### `healthy` +#### `health_check` This is used to check whether the installed environment is considered healthy. -This function should return `True` or `False`. +This function should return a detailed message if unhealthy or `None` if +healthy. You generally don't need to implement this on a first pass and can just use: ```python -healthy = helpers.basic_healthy +health_check = helpers.basic_healthy_check ``` `python` is currently the only language which implements this api, for python diff --git a/azure-pipelines.yml b/azure-pipelines.yml index afb2982..454f6f1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,7 +10,7 @@ resources: type: github endpoint: github name: asottile/azure-pipeline-templates - ref: refs/tags/v2.1.0 + ref: refs/tags/v2.4.1 jobs: - template: job--python-tox.yml@asottile diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index bf4e2e4..9b53e81 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -14,6 +14,8 @@ from identify.identify import ALL_TAGS import pre_commit.constants as C from pre_commit.color import add_color_option +from pre_commit.commands.validate_config import validate_config +from pre_commit.commands.validate_manifest import validate_manifest from pre_commit.errors import FatalError from pre_commit.languages.all import all_languages from pre_commit.logging_handler import logging_handler @@ -100,14 +102,12 @@ def validate_manifest_main(argv: Sequence[str] | None = None) -> int: args = parser.parse_args(argv) with logging_handler(args.color): - ret = 0 - for filename in args.filenames: - try: - load_manifest(filename) - except InvalidManifestError as e: - print(e) - ret = 1 - return ret + logger.warning( + 'pre-commit-validate-manifest is deprecated -- ' + 'use `pre-commit validate-manifest` instead.', + ) + + return validate_manifest(args.filenames) LOCAL = 'local' @@ -409,11 +409,9 @@ def validate_config_main(argv: Sequence[str] | None = None) -> int: args = parser.parse_args(argv) with logging_handler(args.color): - ret = 0 - for filename in args.filenames: - try: - load_config(filename) - except InvalidConfigError as e: - print(e) - ret = 1 - return ret + logger.warning( + 'pre-commit-validate-config is deprecated -- ' + 'use `pre-commit validate-config` instead.', + ) + + return validate_config(args.filenames) diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 18e5e9f..f315c04 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -114,7 +114,8 @@ def _pre_push_ns( remote_url = args[1] for line in stdin.decode().splitlines(): - local_branch, local_sha, remote_branch, remote_sha = line.split() + parts = line.rsplit(maxsplit=3) + local_branch, local_sha, remote_branch, remote_sha = parts if local_sha == Z40: continue elif remote_sha != Z40 and _rev_exists(remote_sha): diff --git a/pre_commit/commands/validate_config.py b/pre_commit/commands/validate_config.py new file mode 100644 index 0000000..91bb017 --- /dev/null +++ b/pre_commit/commands/validate_config.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from pre_commit import clientlib + + +def validate_config(filenames: list[str]) -> int: + ret = 0 + + for filename in filenames: + try: + clientlib.load_config(filename) + except clientlib.InvalidConfigError as e: + print(e) + ret = 1 + + return ret diff --git a/pre_commit/commands/validate_manifest.py b/pre_commit/commands/validate_manifest.py new file mode 100644 index 0000000..372a638 --- /dev/null +++ b/pre_commit/commands/validate_manifest.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from pre_commit import clientlib + + +def validate_manifest(filenames: list[str]) -> int: + ret = 0 + + for filename in filenames: + try: + clientlib.load_manifest(filename) + except clientlib.InvalidManifestError as e: + print(e) + ret = 1 + + return ret diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index cfcbf68..cfd42ce 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -34,7 +34,7 @@ class Language(NamedTuple): # return a value to replace `'default` for `language_version` get_default_version: Callable[[], str] # return whether the environment is healthy (or should be rebuilt) - healthy: Callable[[Prefix, str], bool] + health_check: Callable[[Prefix, str], str | None] # install a repository for the given language and language_version install_environment: Callable[[Prefix, str, Sequence[str]], None] # execute a hook and return the exit code and output @@ -44,25 +44,25 @@ class Language(NamedTuple): # TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018 languages = { # BEGIN GENERATED (testing/gen-languages-all) - 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, healthy=conda.healthy, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 - 'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, healthy=coursier.healthy, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501 - 'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, healthy=dart.healthy, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501 - 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, healthy=docker.healthy, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 - 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, healthy=docker_image.healthy, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 - 'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, healthy=dotnet.healthy, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501 - 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, healthy=fail.healthy, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 - 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, healthy=golang.healthy, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 - 'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, healthy=lua.healthy, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501 - 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, healthy=node.healthy, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 - 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, healthy=perl.healthy, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 - 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 - 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 - 'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, healthy=r.healthy, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501 - 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 - 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 - 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 - 'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, healthy=swift.healthy, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501 - 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, healthy=system.healthy, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 + 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, health_check=conda.health_check, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 + 'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, health_check=coursier.health_check, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501 + 'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, health_check=dart.health_check, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501 + 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, health_check=docker.health_check, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 + 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, health_check=docker_image.health_check, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 + 'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, health_check=dotnet.health_check, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501 + 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, health_check=fail.health_check, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 + 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, health_check=golang.health_check, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 + 'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, health_check=lua.health_check, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501 + 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, health_check=node.health_check, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 + 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, health_check=perl.health_check, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 + 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, health_check=pygrep.health_check, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 + 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, health_check=python.health_check, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 + 'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, health_check=r.health_check, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501 + 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, health_check=ruby.health_check, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 + 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, health_check=rust.health_check, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 + 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, health_check=script.health_check, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 + 'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, health_check=swift.health_check, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501 + 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, health_check=system.health_check, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 # END GENERATED } # TODO: fully deprecate `python_venv` diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 88ac53f..f0195e4 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -18,7 +18,7 @@ from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'conda' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(env: str) -> PatchesT: diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index bb3e0b8..9fe43eb 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -17,7 +17,7 @@ from pre_commit.util import clean_path_on_failure ENVIRONMENT_DIR = 'coursier' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def install_environment( diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 65135f8..55ecbf4 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -21,7 +21,7 @@ from pre_commit.util import yaml_load ENVIRONMENT_DIR = 'dartenv' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index af1860c..eea9f76 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -16,7 +16,7 @@ from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'docker' PRE_COMMIT_LABEL = 'PRE_COMMIT' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def _is_in_docker() -> bool: diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index ccc1d67..daa4d1b 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -8,7 +8,7 @@ from pre_commit.languages.docker import docker_cmd ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index a16e7f0..3983c6f 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -18,7 +18,7 @@ ENVIRONMENT_DIR = 'dotnetenv' BIN_DIR = 'bin' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: @@ -59,22 +59,19 @@ def install_environment( # Determine tool from the packaged file <tool_name>.<version>.nupkg build_outputs = os.listdir(os.path.join(prefix.prefix_dir, build_dir)) - if len(build_outputs) != 1: - raise NotImplementedError( - f"Can't handle multiple build outputs. Got {build_outputs}", + for output in build_outputs: + tool_name = output.split('.')[0] + + # Install to bin dir + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'tool', 'install', + '--tool-path', os.path.join(envdir, BIN_DIR), + '--add-source', build_dir, + tool_name, + ), ) - tool_name = build_outputs[0].split('.')[0] - - # Install to bin dir - helpers.run_setup_cmd( - prefix, - ( - 'dotnet', 'tool', 'install', - '--tool-path', os.path.join(envdir, BIN_DIR), - '--add-source', build_dir, - tool_name, - ), - ) # Clean the git dir, ignoring the environment dir clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*') diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index 4cb95af..00b06a9 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -7,7 +7,7 @@ from pre_commit.languages import helpers ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 759c268..a5f9dba 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -21,7 +21,7 @@ from pre_commit.util import rmtree ENVIRONMENT_DIR = 'golangenv' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 8080826..05a7165 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -88,8 +88,8 @@ def basic_get_default_version() -> str: return C.DEFAULT -def basic_healthy(prefix: Prefix, language_version: str) -> bool: - return True +def basic_health_check(prefix: Prefix, language_version: str) -> str | None: + return None def no_install( diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index 38bdf54..49aa730 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -18,7 +18,7 @@ from pre_commit.util import cmd_output ENVIRONMENT_DIR = 'lua_env' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def _get_lua_version() -> str: # pragma: win32 no cover diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index b084e8f..39f3000 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -73,10 +73,13 @@ def in_env( yield -def healthy(prefix: Prefix, language_version: str) -> bool: +def health_check(prefix: Prefix, language_version: str) -> str | None: with in_env(prefix, language_version): retcode, _, _ = cmd_output_b('node', '--version', retcode=None) - return retcode == 0 + if retcode != 0: # pragma: win32 no cover + return f'`node --version` returned {retcode}' + else: + return None def install_environment( diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 0eee258..78bd65a 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -16,7 +16,7 @@ from pre_commit.util import clean_path_on_failure ENVIRONMENT_DIR = 'perl_env' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def _envdir(prefix: Prefix, version: str) -> str: diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index f2758c5..2e2072b 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -14,7 +14,7 @@ from pre_commit.xargs import xargs ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 668ba35..19fa247 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -163,27 +163,44 @@ def in_env( yield -def healthy(prefix: Prefix, language_version: str) -> bool: +def health_check(prefix: Prefix, language_version: str) -> str | None: directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) envdir = prefix.path(directory) pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg') # created with "old" virtualenv if not os.path.exists(pyvenv_cfg): - return False + return 'pyvenv.cfg does not exist (old virtualenv?)' exe_name = win_exe('python') py_exe = prefix.path(bin_dir(envdir), exe_name) cfg = _read_pyvenv_cfg(pyvenv_cfg) - return ( - 'version_info' in cfg and - # always use uncached lookup here in case we replaced an unhealthy env - _version_info.__wrapped__(py_exe) == cfg['version_info'] and ( - 'base-executable' not in cfg or - _version_info(cfg['base-executable']) == cfg['version_info'] + if 'version_info' not in cfg: + return "created virtualenv's pyvenv.cfg is missing `version_info`" + + # always use uncached lookup here in case we replaced an unhealthy env + virtualenv_version = _version_info.__wrapped__(py_exe) + if virtualenv_version != cfg['version_info']: + return ( + f'virtualenv python version did not match created version:\n' + f'- actual version: {virtualenv_version}\n' + f'- expected version: {cfg["version_info"]}\n' ) - ) + + # made with an older version of virtualenv? skip `base-executable` check + if 'base-executable' not in cfg: + return None + + base_exe_version = _version_info(cfg['base-executable']) + if base_exe_version != cfg['version_info']: + return ( + f'base executable python version does not match created version:\n' + f'- base-executable version: {base_exe_version}\n' + f'- expected version: {cfg["version_info"]}\n' + ) + else: + return None def install_environment( diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index c736b38..40a001d 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -19,7 +19,7 @@ from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'renv' RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index ae64492..6c5cff2 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -21,7 +21,7 @@ from pre_commit.util import clean_path_on_failure from pre_commit.util import resource_bytesio ENVIRONMENT_DIR = 'rbenv' -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check @functools.lru_cache(maxsize=1) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 39e3628..01c3730 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -19,7 +19,7 @@ from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'rustenv' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(target_dir: str) -> PatchesT: diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index 2844b5e..d5e7677 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -7,7 +7,7 @@ from pre_commit.languages import helpers ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index c630953..4c68703 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -17,7 +17,7 @@ from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'swift_env' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check BUILD_DIR = '.build' BUILD_CONFIG = 'release' diff --git a/pre_commit/languages/system.py b/pre_commit/languages/system.py index 9846c98..c64fb36 100644 --- a/pre_commit/languages/system.py +++ b/pre_commit/languages/system.py @@ -8,7 +8,7 @@ from pre_commit.languages import helpers ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/main.py b/pre_commit/main.py index 645e97f..6d2814b 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -21,6 +21,8 @@ from pre_commit.commands.migrate_config import migrate_config from pre_commit.commands.run import run from pre_commit.commands.sample_config import sample_config from pre_commit.commands.try_repo import try_repo +from pre_commit.commands.validate_config import validate_config +from pre_commit.commands.validate_manifest import validate_manifest from pre_commit.error_handler import error_handler from pre_commit.logging_handler import logging_handler from pre_commit.store import Store @@ -34,8 +36,10 @@ logger = logging.getLogger('pre_commit') # pyvenv os.environ.pop('__PYVENV_LAUNCHER__', None) - -COMMANDS_NO_GIT = {'clean', 'gc', 'init-templatedir', 'sample-config'} +COMMANDS_NO_GIT = { + 'clean', 'gc', 'init-templatedir', 'sample-config', + 'validate-config', 'validate-manifest', +} def _add_config_option(parser: argparse.ArgumentParser) -> None: @@ -304,6 +308,20 @@ def main(argv: Sequence[str] | None = None) -> int: _add_config_option(uninstall_parser) _add_hook_type_option(uninstall_parser) + validate_config_parser = subparsers.add_parser( + 'validate-config', help='Validate .pre-commit-config.yaml files', + ) + add_color_option(validate_config_parser) + _add_config_option(validate_config_parser) + validate_config_parser.add_argument('filenames', nargs='*') + + validate_manifest_parser = subparsers.add_parser( + 'validate-manifest', help='Validate .pre-commit-hooks.yaml files', + ) + add_color_option(validate_manifest_parser) + _add_config_option(validate_manifest_parser) + validate_manifest_parser.add_argument('filenames', nargs='*') + help = subparsers.add_parser( 'help', help='Show help for a specific command.', ) @@ -378,6 +396,10 @@ def main(argv: Sequence[str] | None = None) -> int: config_file=args.config, hook_types=args.hook_types, ) + elif args.command == 'validate-config': + return validate_config(args.filenames) + elif args.command == 'validate-manifest': + return validate_manifest(args.filenames) else: raise NotImplementedError( f'Command {args.command} not implemented.', diff --git a/pre_commit/repository.py b/pre_commit/repository.py index ac5d294..4092277 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -57,7 +57,7 @@ def _hook_installed(hook: Hook) -> bool: _read_state(hook.prefix, venv) == _state(hook.additional_dependencies) ) and - lang.healthy(hook.prefix, hook.language_version) + not lang.health_check(hook.prefix, hook.language_version) ) ) @@ -79,11 +79,13 @@ def _hook_install(hook: Hook) -> None: lang.install_environment( hook.prefix, hook.language_version, hook.additional_dependencies, ) - if not lang.healthy(hook.prefix, hook.language_version): + health_error = lang.health_check(hook.prefix, hook.language_version) + if health_error: raise AssertionError( - f'BUG: expected environment for {hook.language} to be healthy() ' + f'BUG: expected environment for {hook.language} to be healthy ' f'immediately after install, please open an issue describing ' - f'your environment', + f'your environment\n\n' + f'more info:\n\n{health_error}', ) # Write our state to indicate we're installed _write_state(hook.prefix, venv, _state(hook.additional_dependencies)) diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz Binary files differindex e248c57..8edb3ca 100644 --- a/pre_commit/resources/ruby-build.tar.gz +++ b/pre_commit/resources/ruby-build.tar.gz @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 2.18.1 +version = 2.19.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown diff --git a/testing/gen-languages-all b/testing/gen-languages-all index dfd92c0..05f8929 100755 --- a/testing/gen-languages-all +++ b/testing/gen-languages-all @@ -3,15 +3,15 @@ from __future__ import annotations import sys -LANGUAGES = [ +LANGUAGES = ( 'conda', 'coursier', 'dart', 'docker', 'docker_image', 'dotnet', 'fail', 'golang', 'lua', 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust', 'script', 'swift', 'system', -] -FIELDS = [ - 'ENVIRONMENT_DIR', 'get_default_version', 'healthy', 'install_environment', - 'run_hook', -] +) +FIELDS = ( + 'ENVIRONMENT_DIR', 'get_default_version', 'health_check', + 'install_environment', 'run_hook', +) def main() -> int: diff --git a/testing/get-swift.sh b/testing/get-swift.sh index a05b7b9..b77e18c 100755 --- a/testing/get-swift.sh +++ b/testing/get-swift.sh @@ -3,9 +3,9 @@ set -euo pipefail . /etc/lsb-release -if [ "$DISTRIB_CODENAME" = "bionic" ]; then - SWIFT_URL='https://swift.org/builds/swift-5.1.3-release/ubuntu1804/swift-5.1.3-RELEASE/swift-5.1.3-RELEASE-ubuntu18.04.tar.gz' - SWIFT_HASH='ac82ccd773fe3d586fc340814e31e120da1ff695c6a712f6634e9cc720769610' +if [ "$DISTRIB_CODENAME" = "focal" ]; then + SWIFT_URL='https://download.swift.org/swift-5.6.1-release/ubuntu2004/swift-5.6.1-RELEASE/swift-5.6.1-RELEASE-ubuntu20.04.tar.gz' + SWIFT_HASH='2b4f22d4a8b59fe8e050f0b7f020f8d8f12553cbda56709b2340a4a3bb90cfea' else echo "unknown dist: ${DISTRIB_CODENAME}" 1>&2 exit 1 diff --git a/testing/make-archives b/testing/make-archives index 1b825fe..04b42dd 100755 --- a/testing/make-archives +++ b/testing/make-archives @@ -17,7 +17,7 @@ from typing import Sequence REPOS = ( ('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'), - ('ruby-build', 'https://github.com/rbenv/ruby-build', 'a5ca3e4'), + ('ruby-build', 'https://github.com/rbenv/ruby-build', '2004fd7'), ( 'ruby-download', 'https://github.com/garnieretienne/rvm-download', diff --git a/testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml new file mode 100644 index 0000000..f221854 --- /dev/null +++ b/testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml @@ -0,0 +1,12 @@ +- id: dotnet-example-hook + name: Test Project 1 + description: Test Project 1 + entry: proj1 + language: dotnet + stages: [commit] +- id: proj2 + name: Test Project 2 + description: Test Project 2 + entry: proj2 + language: dotnet + stages: [commit] diff --git a/testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln b/testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln new file mode 100644 index 0000000..edb0fcb --- /dev/null +++ b/testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj1", "proj1\proj1.csproj", "{38A939C3-DEA4-47D7-9B75-0418C4249662}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj2", "proj2\proj2.csproj", "{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.Build.0 = Release|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs b/testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs new file mode 100644 index 0000000..03876f5 --- /dev/null +++ b/testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace proj1 +{ + class Program + { + static void Main(string[] args) + { + Console.Write("Hello from dotnet!\n"); + } + } +} diff --git a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj b/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj new file mode 100644 index 0000000..4f714d3 --- /dev/null +++ b/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net5.0</TargetFramework> + + <PackAsTool>true</PackAsTool> + <ToolCommandName>proj1</ToolCommandName> + <PackageOutputPath>./nupkg</PackageOutputPath> + </PropertyGroup> + +</Project> diff --git a/testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs b/testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs new file mode 100644 index 0000000..47a99a3 --- /dev/null +++ b/testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace proj2 +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj b/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj new file mode 100644 index 0000000..da451f7 --- /dev/null +++ b/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net5.0</TargetFramework> + + <PackAsTool>true</PackAsTool> + <ToolCommandName>proj2</ToolCommandName> + <PackageOutputPath>./nupkg</PackageOutputPath> + </PropertyGroup> + +</Project> diff --git a/testing/resources/golang_hooks_repo/go.mod b/testing/resources/golang_hooks_repo/go.mod index 523bfc9..f37d4b6 100644 --- a/testing/resources/golang_hooks_repo/go.mod +++ b/testing/resources/golang_hooks_repo/go.mod @@ -1 +1,5 @@ module golang-hello-world + +go 1.18 + +require github.com/BurntSushi/toml v1.1.0 diff --git a/testing/resources/golang_hooks_repo/go.sum b/testing/resources/golang_hooks_repo/go.sum new file mode 100644 index 0000000..ec0c385 --- /dev/null +++ b/testing/resources/golang_hooks_repo/go.sum @@ -0,0 +1,2 @@ +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 3fb3af5..fb36bb5 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -122,8 +122,8 @@ def test_validate_config_old_list_format_ok(tmpdir, cap_out): f = tmpdir.join('cfg.yaml') f.write('- {repo: meta, hooks: [{id: identity}]}') assert not validate_config_main((f.strpath,)) - start = '[WARNING] normalizing pre-commit configuration to a top-level map' - assert cap_out.get().startswith(start) + msg = '[WARNING] normalizing pre-commit configuration to a top-level map' + assert msg in cap_out.get() def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog): @@ -142,6 +142,12 @@ def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog): ( 'pre_commit', logging.WARNING, + 'pre-commit-validate-config is deprecated -- ' + 'use `pre-commit validate-config` instead.', + ), + ( + 'pre_commit', + logging.WARNING, 'Unexpected key(s) present on https://gitlab.com/pycqa/flake8: ' 'args', ), @@ -165,6 +171,12 @@ def test_validate_warn_on_unknown_keys_at_top_level(tmpdir, caplog): ( 'pre_commit', logging.WARNING, + 'pre-commit-validate-config is deprecated -- ' + 'use `pre-commit validate-config` instead.', + ), + ( + 'pre_commit', + logging.WARNING, 'Unexpected key(s) present at root: foo', ), ] diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py index b0159f8..3e20874 100644 --- a/tests/commands/hook_impl_test.py +++ b/tests/commands/hook_impl_test.py @@ -242,6 +242,18 @@ def test_run_ns_pre_push_new_branch_existing_rev(push_example): 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 diff --git a/tests/languages/helpers_test.py b/tests/languages/helpers_test.py index 259cb97..f333e79 100644 --- a/tests/languages/helpers_test.py +++ b/tests/languages/helpers_test.py @@ -67,8 +67,8 @@ def test_basic_get_default_version(): assert helpers.basic_get_default_version() == C.DEFAULT -def test_basic_healthy(): - assert helpers.basic_healthy(Prefix('.'), 'default') is True +def test_basic_health_check(): + assert helpers.basic_health_check(Prefix('.'), 'default') is None def test_failed_setup_command_does_not_unicode_error(): diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py index fb5ae71..b69adfa 100644 --- a/tests/languages/node_test.py +++ b/tests/languages/node_test.py @@ -62,7 +62,7 @@ def test_healthy_system_node(tmpdir): prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) - assert node.healthy(prefix, 'system') + assert node.health_check(prefix, 'system') is None @xfailif_windows # pragma: win32 no cover @@ -78,10 +78,11 @@ def test_unhealthy_if_system_node_goes_missing(tmpdir): with envcontext.envcontext((path,)): prefix = Prefix(str(prefix_dir)) node.install_environment(prefix, 'system', ()) - assert node.healthy(prefix, 'system') + assert node.health_check(prefix, 'system') is None node_bin.remove() - assert not node.healthy(prefix, 'system') + ret = node.health_check(prefix, 'system') + assert ret == '`node --version` returned 127' @xfailif_windows # pragma: win32 no cover @@ -101,7 +102,7 @@ def test_installs_without_links_outside_env(tmpdir): prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) - assert node.healthy(prefix, 'system') + assert node.health_check(prefix, 'system') is None # this directory shouldn't exist, make sure we succeed without it existing cmd_output('rm', '-rf', str(tmpdir.join('node_modules'))) diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index 6160669..54fb98f 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -93,11 +93,11 @@ def test_healthy_default_creator(python_dir): python.install_environment(prefix, C.DEFAULT, ()) # should be healthy right after creation - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None # even if a `types.py` file exists, should still be healthy tmpdir.join('types.py').ensure() - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None def test_healthy_venv_creator(python_dir): @@ -107,7 +107,7 @@ def test_healthy_venv_creator(python_dir): with envcontext((('VIRTUALENV_CREATOR', 'venv'),)): python.install_environment(prefix, C.DEFAULT, ()) - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None def test_unhealthy_python_goes_missing(python_dir): @@ -119,7 +119,12 @@ def test_unhealthy_python_goes_missing(python_dir): py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name) os.remove(py_exe) - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'virtualenv python version did not match created version:\n' + f'- actual version: <<error retrieving version from {py_exe}>>\n' + f'- expected version: {python._version_info(sys.executable)}\n' + ) def test_unhealthy_with_version_change(python_dir): @@ -127,10 +132,15 @@ def test_unhealthy_with_version_change(python_dir): python.install_environment(prefix, C.DEFAULT, ()) - with open(prefix.path('py_env-default/pyvenv.cfg'), 'w') as f: + with open(prefix.path('py_env-default/pyvenv.cfg'), 'a+') as f: f.write('version_info = 1.2.3\n') - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'virtualenv python version did not match created version:\n' + f'- actual version: {python._version_info(sys.executable)}\n' + f'- expected version: 1.2.3\n' + ) def test_unhealthy_system_version_changes(python_dir): @@ -141,7 +151,12 @@ def test_unhealthy_system_version_changes(python_dir): with open(prefix.path('py_env-default/pyvenv.cfg'), 'a') as f: f.write('base-executable = /does/not/exist\n') - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'base executable python version does not match created version:\n' + f'- base-executable version: <<error retrieving version from /does/not/exist>>\n' # noqa: E501 + f'- expected version: {python._version_info(sys.executable)}\n' + ) def test_unhealthy_old_virtualenv(python_dir): @@ -152,7 +167,21 @@ def test_unhealthy_old_virtualenv(python_dir): # simulate "old" virtualenv by deleting this file os.remove(prefix.path('py_env-default/pyvenv.cfg')) - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == 'pyvenv.cfg does not exist (old virtualenv?)' + + +def test_unhealthy_unexpected_pyvenv(python_dir): + prefix, tmpdir = python_dir + + python.install_environment(prefix, C.DEFAULT, ()) + + # simulate a buggy environment build (I don't think this is possible) + with open(prefix.path('py_env-default/pyvenv.cfg'), 'w'): + pass + + ret = python.health_check(prefix, C.DEFAULT) + assert ret == "created virtualenv's pyvenv.cfg is missing `version_info`" def test_unhealthy_then_replaced(python_dir): @@ -170,9 +199,14 @@ def test_unhealthy_then_replaced(python_dir): make_executable(py_exe) # should be unhealthy due to version mismatch - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'virtualenv python version did not match created version:\n' + f'- actual version: 1.2.3\n' + f'- expected version: {python._version_info(sys.executable)}\n' + ) # now put the exe back and it should be healthy again os.replace(f'{py_exe}.tmp', py_exe) - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None diff --git a/tests/main_test.py b/tests/main_test.py index a645300..a7afd6d 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -79,6 +79,7 @@ def test_adjust_args_try_repo_repo_relative(in_git_dir): FNS = ( 'autoupdate', 'clean', 'gc', 'hook_impl', 'install', 'install_hooks', 'migrate_config', 'run', 'sample_config', 'uninstall', + 'validate_config', 'validate_manifest', ) CMDS = tuple(fn.replace('_', '-') for fn in FNS) diff --git a/tests/repository_test.py b/tests/repository_test.py index cef6887..3729ab1 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -173,6 +173,7 @@ def test_python_venv(tempdir_factory, store): ) +@xfailif_windows # pragma: win32 no cover # no python 2 in GHA def test_switch_language_versions_doesnt_clobber(tempdir_factory, store): # We're using the python3 repo because it prints the python version path = make_repo(tempdir_factory, 'python3_hooks_repo') @@ -667,7 +668,7 @@ def test_additional_golang_dependencies_installed( path = make_repo(tempdir_factory, 'golang_hooks_repo') config = make_config_from_repo(path) # A small go package - deps = ['golang.org/x/example/hello'] + deps = ['golang.org/x/example/hello@latest'] config['hooks'][0]['additional_dependencies'] = deps hook = _get_hook(config, store, 'golang-hook') binaries = os.listdir( @@ -688,7 +689,7 @@ def test_local_golang_additional_dependencies(store): 'name': 'hello', 'entry': 'hello', 'language': 'golang', - 'additional_dependencies': ['golang.org/x/example/hello'], + 'additional_dependencies': ['golang.org/x/example/hello@latest'], }], } hook = _get_hook(config, store, 'hello') @@ -892,6 +893,7 @@ def test_local_python_repo(store, local_python_config): assert _norm_out(out) == b"3\n['filename']\nHello World\n" +@xfailif_windows # pragma: win32 no cover # no python2 in GHA def test_local_python_repo_python2(store, local_python_config): local_python_config['hooks'][0]['language_version'] = 'python2' hook = _get_hook(local_python_config, store, 'python3-hook') @@ -1042,6 +1044,7 @@ def test_local_perl_additional_dependencies(store): ( 'dotnet_hooks_csproj_repo', 'dotnet_hooks_sln_repo', + 'dotnet_hooks_combo_repo', ), ) def test_dotnet_hook(tempdir_factory, store, repo): |