diff --git a/pyproject.toml b/pyproject.toml index a5c15db73f..8800d86bcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -236,7 +236,6 @@ ignore = [ "D102", # Missing docstring in public method (currently in too many places) "FBT", "PLR", - "PTH", "TRY", ] select = ["ALL"] @@ -257,6 +256,11 @@ known-first-party = ["ansiblelint"] "test/**/*.py" = ["S"] "src/ansiblelint/rules/*.py" = ["S"] "src/ansiblelint/testing/*.py" = ["S"] +# Temporary disabled until we fix them: +"src/ansiblelint/{testing,schemas,rules}/*.py" = ["PTH"] +"src/ansiblelint/{utils,file_utils,runner,loaders,constants,config,cli,_mockings,__main__}.py" = [ + "PTH", +] [tool.setuptools.dynamic] optional-dependencies.docs = { file = [".config/requirements-docs.txt"] } diff --git a/src/ansiblelint/__main__.py b/src/ansiblelint/__main__.py index 78a290a022..96d55c0978 100755 --- a/src/ansiblelint/__main__.py +++ b/src/ansiblelint/__main__.py @@ -31,6 +31,7 @@ import subprocess import sys from contextlib import contextmanager +from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, TextIO from ansible_compat.config import ansible_version @@ -226,11 +227,11 @@ def main(argv: list[str] | None = None) -> int: # noqa: C901 for level, message in log_entries: _logger.log(level, message) _logger.debug("Options: %s", options) - _logger.debug(os.getcwd()) + _logger.debug("CWD: %s", Path.cwd()) if not options.offline: # pylint: disable=import-outside-toplevel - from ansiblelint.schemas import refresh_schemas + from ansiblelint.schemas.__main__ import refresh_schemas refresh_schemas() diff --git a/src/ansiblelint/_mockings.py b/src/ansiblelint/_mockings.py index 00fb936907..b2440ccc78 100644 --- a/src/ansiblelint/_mockings.py +++ b/src/ansiblelint/_mockings.py @@ -3,31 +3,46 @@ import contextlib import logging -import os import re import sys +from typing import TYPE_CHECKING from ansiblelint.config import options from ansiblelint.constants import ANSIBLE_MOCKED_MODULE, RC +if TYPE_CHECKING: + from pathlib import Path + _logger = logging.getLogger(__name__) def _make_module_stub(module_name: str) -> None: + if not options.cache_dir: + msg = "Cache directory not set" + raise RuntimeError(msg) # a.b.c is treated a collection if re.match(r"^(\w+|\w+\.\w+\.[\.\w]+)$", module_name): parts = module_name.split(".") if len(parts) < 3: - path = f"{options.cache_dir}/modules" + path = options.cache_dir / "modules" module_file = f"{options.cache_dir}/modules/{module_name}.py" namespace = None collection = None else: namespace = parts[0] collection = parts[1] - path = f"{ options.cache_dir }/collections/ansible_collections/{ namespace }/{ collection }/plugins/modules/{ '/'.join(parts[2:-1]) }" + path = ( + options.cache_dir + / "collections" + / "ansible_collections" + / namespace + / collection + / "plugins" + / "modules" + / ("/".join(parts[2:-1])) + ) module_file = f"{path}/{parts[-1]}.py" - os.makedirs(path, exist_ok=True) + path.mkdir(exist_ok=True, parents=True) _write_module_stub( filename=module_file, name=module_file, @@ -58,17 +73,29 @@ def _write_module_stub( # pylint: disable=too-many-branches def _perform_mockings() -> None: """Mock modules and roles.""" + path: Path + if not options.cache_dir: + msg = "Cache directory not set" + raise RuntimeError(msg) for role_name in options.mock_roles: if re.match(r"\w+\.\w+\.\w+$", role_name): namespace, collection, role_dir = role_name.split(".") - path = f"{options.cache_dir}/collections/ansible_collections/{ namespace }/{ collection }/roles/{ role_dir }/" + path = ( + options.cache_dir + / "collections" + / "ansible_collections" + / namespace + / collection + / "roles" + / role_dir + ) else: - path = f"{options.cache_dir}/roles/{role_name}" + path = options.cache_dir / "roles" / role_name # Avoid error from makedirs if destination is a broken symlink - if os.path.islink(path) and not os.path.exists(path): # pragma: no cover + if path.is_symlink() and not path.exists(): # pragma: no cover _logger.warning("Removed broken symlink from %s", path) - os.unlink(path) - os.makedirs(path, exist_ok=True) + path.unlink(missing_ok=True) + path.mkdir(exist_ok=True, parents=True) if options.mock_modules: for module_name in options.mock_modules: @@ -77,11 +104,22 @@ def _perform_mockings() -> None: def _perform_mockings_cleanup() -> None: """Clean up mocked modules and roles.""" + if not options.cache_dir: + msg = "Cache directory not set" + raise RuntimeError(msg) for role_name in options.mock_roles: if re.match(r"\w+\.\w+\.\w+$", role_name): namespace, collection, role_dir = role_name.split(".") - path = f"{options.cache_dir}/collections/ansible_collections/{ namespace }/{ collection }/roles/{ role_dir }/" + path = ( + options.cache_dir + / "collections" + / "ansible_collections" + / namespace + / collection + / "roles" + / role_dir + ) else: - path = f"{options.cache_dir}/roles/{role_name}" + path = options.cache_dir / "roles" / role_name with contextlib.suppress(OSError): - os.rmdir(path) + path.unlink() diff --git a/src/ansiblelint/_vendor/__init__.py b/src/ansiblelint/_vendor/__init__.py index 44c753e70f..4beebc7bc7 100644 --- a/src/ansiblelint/_vendor/__init__.py +++ b/src/ansiblelint/_vendor/__init__.py @@ -1,7 +1,7 @@ -import os import pkgutil import sys import warnings +from pathlib import Path # This package exists to host vendored top-level Python packages for downstream packaging. Any Python packages # installed beneath this one will be masked from the Ansible loader, and available from the front of sys.path. @@ -18,7 +18,7 @@ def _ensure_vendored_path_entry() -> None: """Ensure that any downstream-bundled content beneath this package is available at the top of sys.path.""" # patch our vendored dir onto sys.path - vendored_path_entry = os.path.dirname(__file__) + vendored_path_entry = str(Path(__file__).parent) vendored_module_names = { m[1] for m in pkgutil.iter_modules([vendored_path_entry], "") } # m[1] == m.name diff --git a/src/ansiblelint/app.py b/src/ansiblelint/app.py index 7fac4561ee..c86b87c80d 100644 --- a/src/ansiblelint/app.py +++ b/src/ansiblelint/app.py @@ -5,6 +5,7 @@ import logging import os from functools import lru_cache +from pathlib import Path from typing import TYPE_CHECKING, Any from ansible_compat.runtime import Runtime @@ -101,7 +102,7 @@ def render_matches(self, matches: list[MatchError]) -> None: # noqa: C901 if self.options.sarif_file: sarif = formatters.SarifFormatter(self.options.cwd, True) json = sarif.format_result(matches) - with open(self.options.sarif_file, "w", encoding="utf-8") as sarif_file: + with self.options.sarif_file.open("w", encoding="utf-8") as sarif_file: sarif_file.write(json) def count_results(self, matches: list[MatchError]) -> SummarizedResults: @@ -177,11 +178,12 @@ def report_outcome(self, result: LintResult, mark_as_success: bool = False) -> i matched_rules = self._get_matched_skippable_rules(result.matches) if matched_rules and self.options.generate_ignore: - console_stderr.print(f"Writing ignore file to {IGNORE_FILE.default}") + ignore_file_path = Path(IGNORE_FILE.default) + console_stderr.print(f"Writing ignore file to {ignore_file_path}") lines = set() for rule in result.matches: lines.add(f"{rule.filename} {rule.tag}\n") - with open(IGNORE_FILE.default, "w", encoding="utf-8") as ignore_file: + with ignore_file_path.open("w", encoding="utf-8") as ignore_file: ignore_file.write( "# This file contains ignores rule violations for ansible-lint\n", ) diff --git a/src/ansiblelint/cli.py b/src/ansiblelint/cli.py index ae926b574e..96955115c8 100644 --- a/src/ansiblelint/cli.py +++ b/src/ansiblelint/cli.py @@ -272,7 +272,12 @@ def get_cli_parser() -> argparse.ArgumentParser: ], help="stdout formatting, json being an alias for codeclimate. (default: %(default)s)", ) - parser.add_argument("--sarif-file", default=None, help="SARIF output file") + parser.add_argument( + "--sarif-file", + default=None, + type=Path, + help="SARIF output file", + ) parser.add_argument( "-q", dest="quiet", diff --git a/src/ansiblelint/constants.py b/src/ansiblelint/constants.py index 8385f6bcf7..91b47085b8 100644 --- a/src/ansiblelint/constants.py +++ b/src/ansiblelint/constants.py @@ -1,6 +1,7 @@ """Constants used by AnsibleLint.""" import os.path from enum import Enum +from pathlib import Path from typing import Literal DEFAULT_RULESDIR = os.path.join(os.path.dirname(__file__), "rules") @@ -162,7 +163,7 @@ def main(): # reusable actions, where the mounted volume might have different owner. # # https://github.com/ansible/ansible-lint-action/issues/138 -GIT_CMD = ["git", "-c", f"safe.directory={os.getcwd()}"] +GIT_CMD = ["git", "-c", f"safe.directory={Path.cwd()}"] CONFIG_FILENAMES = [".ansible-lint", ".config/ansible-lint.yml"] diff --git a/src/ansiblelint/rules/__init__.py b/src/ansiblelint/rules/__init__.py index 92a6cae3ab..dd9731082b 100644 --- a/src/ansiblelint/rules/__init__.py +++ b/src/ansiblelint/rules/__init__.py @@ -369,7 +369,7 @@ class RulesCollection: def __init__( self, - rulesdirs: list[str] | None = None, + rulesdirs: list[str] | list[Path] | None = None, options: Options = default_options, profile_name: str | None = None, conditional: bool = True, @@ -379,9 +379,8 @@ def __init__( self.profile = [] if profile_name: self.profile = PROFILES[profile_name] - if rulesdirs is None: - rulesdirs = [] - self.rulesdirs = expand_paths_vars(rulesdirs) + rulesdirs_str = [] if rulesdirs is None else [str(r) for r in rulesdirs] + self.rulesdirs = expand_paths_vars(rulesdirs_str) self.rules: list[BaseRule] = [] # internal rules included in order to expose them for docs as they are # not directly loaded by our rule loader. @@ -393,7 +392,7 @@ def __init__( WarningRule(), ], ) - for rule in load_plugins(rulesdirs): + for rule in load_plugins(rulesdirs_str): self.register(rule, conditional=conditional) self.rules = sorted(self.rules) diff --git a/src/ansiblelint/runner.py b/src/ansiblelint/runner.py index 029d623121..cf1ae7d949 100644 --- a/src/ansiblelint/runner.py +++ b/src/ansiblelint/runner.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: from collections.abc import Generator + from pathlib import Path from ansiblelint.config import Options from ansiblelint.rules import RulesCollection @@ -40,7 +41,7 @@ class Runner: # pylint: disable=too-many-arguments,too-many-instance-attributes def __init__( self, - *lintables: Lintable | str, + *lintables: Lintable | str | Path, rules: RulesCollection, tags: frozenset[Any] = frozenset(), skip_list: list[str] | None = None, diff --git a/src/ansiblelint/schemas/__init__.py b/src/ansiblelint/schemas/__init__.py index 1eb63fb841..f1dad4824d 100644 --- a/src/ansiblelint/schemas/__init__.py +++ b/src/ansiblelint/schemas/__init__.py @@ -1,5 +1 @@ """Module containing cached JSON schemas.""" -from ansiblelint.schemas.__main__ import refresh_schemas -from ansiblelint.schemas.main import validate_file_schema - -__all__ = ("refresh_schemas", "validate_file_schema") diff --git a/src/ansiblelint/testing/__init__.py b/src/ansiblelint/testing/__init__.py index d2564a9ba2..660fdadb05 100644 --- a/src/ansiblelint/testing/__init__.py +++ b/src/ansiblelint/testing/__init__.py @@ -97,14 +97,14 @@ def run_role_defaults_main(self, defaults_main_text: str) -> list[MatchError]: def run_ansible_lint( - *argv: str, + *argv: str | Path, cwd: Path | None = None, executable: str | None = None, env: dict[str, str] | None = None, offline: bool = True, ) -> CompletedProcess: """Run ansible-lint on a given path and returns its output.""" - args = [*argv] + args = [str(item) for item in argv] if offline: # pragma: no cover args.insert(0, "--offline") diff --git a/src/ansiblelint/yaml_utils.py b/src/ansiblelint/yaml_utils.py index 82f94e1aab..230ac10fb4 100644 --- a/src/ansiblelint/yaml_utils.py +++ b/src/ansiblelint/yaml_utils.py @@ -8,6 +8,7 @@ import re from collections.abc import Iterator, Sequence from io import StringIO +from pathlib import Path from re import Pattern from typing import TYPE_CHECKING, Any, Callable, Union, cast @@ -85,21 +86,21 @@ def load_yamllint_config() -> YamlLintConfig: config = YamlLintConfig(content=YAMLLINT_CONFIG) # if we detect local yamllint config we use it but raise a warning # as this is likely to get out of sync with our internal config. - for file in [ + for path in [ ".yamllint", ".yamllint.yaml", ".yamllint.yml", os.getenv("YAMLLINT_CONFIG_FILE", ""), - os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) - + "/yamllint/config", + os.getenv("XDG_CONFIG_HOME", "~/.config") + "/yamllint/config", ]: - if os.path.isfile(file): + file = Path(path).expanduser() + if file.is_file(): _logger.debug( "Loading custom %s config file, this extends our " "internal yamllint config.", file, ) - config_override = YamlLintConfig(file=file) + config_override = YamlLintConfig(file=str(file)) config_override.extend(config) config = config_override break diff --git a/test/schemas/src/rebuild.py b/test/schemas/src/rebuild.py index a339d272db..2fab8c0d2b 100644 --- a/test/schemas/src/rebuild.py +++ b/test/schemas/src/rebuild.py @@ -3,6 +3,7 @@ import json import keyword import sys +from pathlib import Path from typing import Any play_keywords = list( @@ -77,7 +78,7 @@ def is_ref_used(obj: Any, ref: str) -> bool: invalid_var_names.remove("__peg_parser__") print("Updating invalid var names") # noqa: T201 - with open("f/vars.json", "r+", encoding="utf-8") as f: + with Path("f/vars.json").open("r+", encoding="utf-8") as f: vars_schema = json.load(f) vars_schema["anyOf"][0]["patternProperties"] = { f"^(?!({'|'.join(invalid_var_names)})$)[a-zA-Z_][\\w]*$": {}, @@ -88,7 +89,7 @@ def is_ref_used(obj: Any, ref: str) -> bool: f.truncate() print("Compiling subschemas...") # noqa: T201 - with open("f/ansible.json", encoding="utf-8") as f: + with Path("f/ansible.json").open(encoding="utf-8") as f: combined_json = json.load(f) for subschema in ["tasks", "playbook"]: @@ -134,6 +135,6 @@ def is_ref_used(obj: Any, ref: str) -> bool: if not spare: break - with open(f"f/{subschema}.json", "w", encoding="utf-8") as f: + with Path(f"f/{subschema}.json").open("w", encoding="utf-8") as f: json.dump(sub_json, f, indent=2, sort_keys=True) f.write("\n") diff --git a/test/test_app.py b/test/test_app.py index 4739a3bfe8..140f5f671d 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -11,11 +11,13 @@ def test_generate_ignore(tmp_path: Path) -> None: lintable = Lintable(tmp_path / "vars.yaml") lintable.content = "foo: bar\nfoo: baz\n" lintable.write(force=True) - assert not (tmp_path / ".ansible-lint-ignore").exists() + ignore_file = tmp_path / ".ansible-lint-ignore" + assert not ignore_file.exists() result = run_ansible_lint(lintable.filename, "--generate-ignore", cwd=tmp_path) assert result.returncode == 2 - assert (tmp_path / ".ansible-lint-ignore").exists() - with open(tmp_path / ".ansible-lint-ignore", encoding="utf-8") as f: + + assert ignore_file.exists() + with ignore_file.open(encoding="utf-8") as f: assert "vars.yaml yaml[key-duplicates]\n" in f.readlines() # Run again and now we expect to succeed as we have an ignore file. result = run_ansible_lint(lintable.filename, cwd=tmp_path) diff --git a/test/test_cli.py b/test/test_cli.py index fcc4ad128f..0a98846a98 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -150,12 +150,14 @@ def test_expand_path_user_and_vars_config_file(base_arguments: list[str]) -> Non ], ) - assert str(config1.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") + assert str(config1.exclude_paths[0]) == os.path.expanduser( # noqa: PTH111 + "~/.ansible/roles", + ) assert str(config1.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") # exclude-paths coming in via cli are PosixPath objects; which hold the (canonical) real path (without symlinks) assert str(config2.exclude_paths[0]) == os.path.realpath( - os.path.expanduser("~/.ansible/roles"), + os.path.expanduser("~/.ansible/roles"), # noqa: PTH111 ) assert str(config2.exclude_paths[1]) == os.path.realpath( os.path.expandvars("$HOME/.ansible/roles"), diff --git a/test/test_cli_role_paths.py b/test/test_cli_role_paths.py index 3ccfd18504..148e1ed3ce 100644 --- a/test/test_cli_role_paths.py +++ b/test/test_cli_role_paths.py @@ -166,7 +166,7 @@ def test_run_single_role_path_with_roles_path_env(local_test_dir: Path) -> None: role_path = "roles/test-role" env = os.environ.copy() - env["ANSIBLE_ROLES_PATH"] = os.path.realpath(os.path.join(cwd, "../examples/roles")) + env["ANSIBLE_ROLES_PATH"] = os.path.realpath((cwd / "../examples/roles").resolve()) result = run_ansible_lint(role_path, cwd=cwd, env=env) assert "Use shell only when shell functionality is required" in result.stdout diff --git a/test/test_eco.py b/test/test_eco.py index b262603206..2da14db365 100644 --- a/test/test_eco.py +++ b/test/test_eco.py @@ -23,7 +23,7 @@ def sanitize_output(text: str) -> str: """Make the output less likely to vary between runs or minor changes.""" # replace full path to home directory with ~. - result = text.replace(os.path.expanduser("~"), "~") + result = text.replace(os.path.expanduser("~"), "~") # noqa: PTH111 # removes warning related to PATH alteration result = re.sub( r"^WARNING: PATH altered to include.+\n", @@ -44,9 +44,9 @@ def test_eco(repo: str) -> None: url = eco_repos[repo] cache_dir = pathlib.Path("~/.cache/ansible-lint-eco").expanduser() my_dir = (pathlib.Path(__file__).parent / "eco").resolve() - os.makedirs(cache_dir, exist_ok=True) + my_dir.mkdir(exist_ok=True, parents=True) # clone repo - if os.path.exists(f"{cache_dir}/{repo}/.git"): + if (cache_dir / repo / ".git").exists(): subprocess.run("git pull", cwd=f"{cache_dir}/{repo}", shell=True, check=True) else: subprocess.run( @@ -76,8 +76,8 @@ def test_eco(repo: str) -> None: result_txt = f"CMD: {shlex.join(result.args)}\n\nRC: {result.returncode}\n\nSTDERR:\n{result.stderr}\n\nSTDOUT:\n{result.stdout}" - os.makedirs(f"{my_dir}/{step}", exist_ok=True) - with open(f"{my_dir}/{step}/{repo}.result", "w", encoding="utf-8") as f: + (my_dir / step).mkdir(exist_ok=True) + with (my_dir / step / "{repo}.result").open("w", encoding="utf-8") as f: f.write(sanitize_output(result_txt)) # fail if result is different than our expected one diff --git a/test/test_file_utils.py b/test/test_file_utils.py index 6f280365ad..6371eb296c 100644 --- a/test/test_file_utils.py +++ b/test/test_file_utils.py @@ -50,7 +50,7 @@ def test_expand_path_vars(monkeypatch: MonkeyPatch) -> None: """Ensure that tilde and env vars are expanded in paths.""" test_path = "/test/path" monkeypatch.setenv("TEST_PATH", test_path) - assert expand_path_vars("~") == os.path.expanduser("~") + assert expand_path_vars("~") == os.path.expanduser("~") # noqa: PTH111 assert expand_path_vars("$TEST_PATH") == test_path @@ -60,7 +60,7 @@ def test_expand_path_vars(monkeypatch: MonkeyPatch) -> None: pytest.param(Path("$TEST_PATH"), "/test/path", id="pathlib.Path"), pytest.param("$TEST_PATH", "/test/path", id="str"), pytest.param(" $TEST_PATH ", "/test/path", id="stripped-str"), - pytest.param("~", os.path.expanduser("~"), id="home"), + pytest.param("~", os.path.expanduser("~"), id="home"), # noqa: PTH:111 ), ) def test_expand_paths_vars( @@ -321,11 +321,11 @@ def test_find_project_root_dotconfig() -> None: # folder already has an .config/ansible-lint.yml file inside, which should # be enough. with cwd(Path("examples")): - assert os.path.exists( + assert Path( ".config/ansible-lint.yml", - ), "Test requires config file inside .config folder." + ).exists(), "Test requires config file inside .config folder." path, method = find_project_root([]) - assert str(path) == str(os.getcwd()) + assert str(path) == str(Path.cwd()) assert ".config/ansible-lint.yml" in method @@ -538,7 +538,11 @@ def test_lintable_content_deleter( ("path", "result"), ( pytest.param("foo", "foo", id="rel"), - pytest.param(os.path.expanduser("~/xxx"), "~/xxx", id="rel-to-home"), + pytest.param( + os.path.expanduser("~/xxx"), # noqa: PTH111 + "~/xxx", + id="rel-to-home", + ), pytest.param("/a/b/c", "/a/b/c", id="absolute"), pytest.param( "examples/playbooks/roles", @@ -562,8 +566,8 @@ def test_bug_2513( we will still be able to process the files. See: https://github.com/ansible/ansible-lint/issues/2513 """ - filename = "~/.cache/ansible-lint/playbook.yml" - os.makedirs(os.path.dirname(os.path.expanduser(filename)), exist_ok=True) + filename = Path("~/.cache/ansible-lint/playbook.yml").expanduser() + filename.parent.mkdir(parents=True, exist_ok=True) lintable = Lintable(filename, content="---\n- hosts: all\n") lintable.write(force=True) with cwd(tmp_path): diff --git a/test/test_formatter_sarif.py b/test/test_formatter_sarif.py index 0661a1061a..8be8109954 100644 --- a/test/test_formatter_sarif.py +++ b/test/test_formatter_sarif.py @@ -167,5 +167,5 @@ def test_sarif_file(file: str, return_code: int) -> None: ] result = subprocess.run([*cmd, file], check=False, capture_output=True) assert result.returncode == return_code - assert os.path.exists(output_file.name) + assert os.path.exists(output_file.name) # noqa: PTH110 assert os.path.getsize(output_file.name) > 0 diff --git a/test/test_list_rules.py b/test/test_list_rules.py index 27d3878032..dab16e3012 100644 --- a/test/test_list_rules.py +++ b/test/test_list_rules.py @@ -1,6 +1,5 @@ """Tests related to our logging/verbosity setup.""" -import os from pathlib import Path import pytest @@ -12,7 +11,7 @@ def test_list_rules_includes_opt_in_rules(project_path: Path) -> None: """Checks that listing rules also includes the opt-in rules.""" # Piggyback off the .yamllint in the root of the repo, just for testing. # We'll "override" it with the one in the fixture. - fakerole = os.path.join("test", "fixtures", "list-rules-tests") + fakerole = Path("test") / "fixtures" / "list-rules-tests" result_list_rules = run_ansible_lint("-L", fakerole, cwd=project_path) @@ -51,7 +50,7 @@ def test_list_rules_with_format_option( """Checks that listing rules with format options works.""" # Piggyback off the .yamllint in the root of the repo, just for testing. # We'll "override" it with the one in the fixture. - fakerole = os.path.join("test", "fixtures", "list-rules-tests") + fakerole = Path("test") / "fixtures" / "list-rules-tests" result_list_rules = run_ansible_lint( "-f", @@ -70,8 +69,8 @@ def test_list_tags_includes_opt_in_rules(project_path: Path) -> None: """Checks that listing tags also includes the opt-in rules.""" # Piggyback off the .yamllint in the root of the repo, just for testing. # We'll "override" it with the one in the fixture. - fakerole = os.path.join("test", "fixtures", "list-rules-tests") + fakerole = Path("test") / "fixtures" / "list-rules-tests" - result_list_tags = run_ansible_lint("-L", fakerole, cwd=project_path) + result_list_tags = run_ansible_lint("-L", str(fakerole), cwd=project_path) assert ("opt-in" in result_list_tags.stdout) is True diff --git a/test/test_loaders.py b/test/test_loaders.py index 7f99690a66..be12cfddb8 100644 --- a/test/test_loaders.py +++ b/test/test_loaders.py @@ -11,7 +11,7 @@ def test_load_ignore_txt_default_empty() -> None: """Test load_ignore_txt when no ignore-file is present.""" with tempfile.TemporaryDirectory() as temporary_directory: - cwd = os.getcwd() + cwd = Path.cwd() try: os.chdir(temporary_directory) @@ -25,9 +25,9 @@ def test_load_ignore_txt_default_empty() -> None: def test_load_ignore_txt_default_success() -> None: """Test load_ignore_txt with an existing ignore-file in the default location.""" with tempfile.TemporaryDirectory() as temporary_directory: - ignore_file = os.path.join(temporary_directory, IGNORE_FILE.default) + ignore_file = Path(temporary_directory) / IGNORE_FILE.default - with open(ignore_file, "w", encoding="utf-8") as _ignore_file: + with ignore_file.open("w", encoding="utf-8") as _ignore_file: _ignore_file.write( dedent( """ @@ -38,7 +38,7 @@ def test_load_ignore_txt_default_success() -> None: ), ) - cwd = os.getcwd() + cwd = Path.cwd() try: os.chdir(temporary_directory) @@ -52,10 +52,10 @@ def test_load_ignore_txt_default_success() -> None: def test_load_ignore_txt_default_success_alternative() -> None: """Test load_ignore_txt with an ignore-file in the alternative location ('.config' subdirectory).""" with tempfile.TemporaryDirectory() as temporary_directory: - ignore_file = os.path.join(temporary_directory, IGNORE_FILE.alternative) - os.makedirs(os.path.dirname(ignore_file)) + ignore_file = Path(temporary_directory) / IGNORE_FILE.alternative + ignore_file.parent.mkdir(parents=True) - with open(ignore_file, "w", encoding="utf-8") as _ignore_file: + with ignore_file.open("w", encoding="utf-8") as _ignore_file: _ignore_file.write( dedent( """ @@ -66,7 +66,7 @@ def test_load_ignore_txt_default_success_alternative() -> None: ), ) - cwd = os.getcwd() + cwd = Path.cwd() try: os.chdir(temporary_directory) @@ -83,10 +83,10 @@ def test_load_ignore_txt_default_success_alternative() -> None: def test_load_ignore_txt_custom_success() -> None: """Test load_ignore_txt with an ignore-file in a user defined location.""" with tempfile.TemporaryDirectory() as temporary_directory: - ignore_file = os.path.join(temporary_directory, "subdir", "my_ignores.txt") - os.makedirs(os.path.dirname(ignore_file)) + ignore_file = Path(temporary_directory) / "subdir" / "my_ignores.txt" + ignore_file.parent.mkdir(parents=True, exist_ok=True) - with open(ignore_file, "w", encoding="utf-8") as _ignore_file: + with ignore_file.open("w", encoding="utf-8") as _ignore_file: _ignore_file.write( dedent( """ @@ -98,7 +98,7 @@ def test_load_ignore_txt_custom_success() -> None: ), ) - cwd = os.getcwd() + cwd = Path.cwd() try: os.chdir(temporary_directory) diff --git a/test/test_main.py b/test/test_main.py index 63d2a96dde..ae11f08f7e 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -20,10 +20,12 @@ def test_call_from_outside_venv(expected_warning: bool) -> None: """Asserts ability to be called w/ or w/o venv activation.""" git_location = shutil.which("git") - assert git_location + if not git_location: + pytest.fail("git not found") + git_path = Path(git_location).parent if expected_warning: - env = {"HOME": str(Path.home()), "PATH": str(os.path.dirname(git_location))} + env = {"HOME": str(Path.home()), "PATH": str(git_path)} else: env = os.environ.copy() @@ -31,12 +33,12 @@ def test_call_from_outside_venv(expected_warning: bool) -> None: if v in os.environ: env[v] = os.environ[v] - py_path = os.path.dirname(sys.executable) + py_path = Path(sys.executable).parent # Passing custom env prevents the process from inheriting PATH or other # environment variables from the current process, so we emulate being # called from outside the venv. proc = subprocess.run( - [f"{py_path}/ansible-lint", "--version"], + [py_path / "ansible-lint", "--version"], check=False, capture_output=True, text=True, diff --git a/test/test_rules_collection.py b/test/test_rules_collection.py index b438d22180..66c69ec903 100644 --- a/test/test_rules_collection.py +++ b/test/test_rules_collection.py @@ -21,8 +21,8 @@ from __future__ import annotations import collections -import os import re +from pathlib import Path import pytest @@ -35,7 +35,7 @@ @pytest.fixture(name="test_rules_collection") def fixture_test_rules_collection() -> RulesCollection: """Create a shared rules collection test instance.""" - return RulesCollection([os.path.abspath("./test/rules/fixtures")]) + return RulesCollection([Path("./test/rules/fixtures").resolve()]) @pytest.fixture(name="ematchtestfile") @@ -131,7 +131,7 @@ def test_skip_non_existent_id( def test_no_duplicate_rule_ids() -> None: """Check that rules of the collection don't have duplicate IDs.""" - real_rules = RulesCollection([os.path.abspath("./src/ansiblelint/rules")]) + real_rules = RulesCollection([Path("./src/ansiblelint/rules").resolve()]) rule_ids = [rule.id for rule in real_rules] assert not any(y > 1 for y in collections.Counter(rule_ids).values()) @@ -142,8 +142,8 @@ def test_rich_rule_listing() -> None: This check also offers the contract of having rule id, short and long descriptions in the console output. """ - rules_path = os.path.abspath("./test/rules/fixtures") - result = run_ansible_lint("-r", rules_path, "-f", "full", "-L") + rules_path = Path("./test/rules/fixtures").resolve() + result = run_ansible_lint("-r", str(rules_path), "-f", "full", "-L") assert result.returncode == 0 for rule in RulesCollection([rules_path]): @@ -157,7 +157,7 @@ def test_rules_id_format() -> None: """Assure all our rules have consistent format.""" rule_id_re = re.compile("^[a-z-]{4,30}$") rules = RulesCollection( - [os.path.abspath("./src/ansiblelint/rules")], + [Path("./src/ansiblelint/rules").resolve()], options=options, conditional=False, ) diff --git a/test/test_runner.py b/test/test_runner.py index 317e94969b..9d8872a397 100644 --- a/test/test_runner.py +++ b/test/test_runner.py @@ -20,38 +20,40 @@ # THE SOFTWARE. from __future__ import annotations -import os +from pathlib import Path from typing import TYPE_CHECKING, Any import pytest from ansiblelint import formatters -from ansiblelint.file_utils import Lintable, abspath +from ansiblelint.file_utils import Lintable from ansiblelint.runner import Runner if TYPE_CHECKING: from ansiblelint.rules import RulesCollection -LOTS_OF_WARNINGS_PLAYBOOK = abspath( - "examples/playbooks/lots_of_warnings.yml", - os.getcwd(), -) +LOTS_OF_WARNINGS_PLAYBOOK = Path("examples/playbooks/lots_of_warnings.yml").resolve() @pytest.mark.parametrize( ("playbook", "exclude", "length"), ( - pytest.param("examples/playbooks/nomatchestest.yml", [], 0, id="nomatchestest"), - pytest.param("examples/playbooks/unicode.yml", [], 1, id="unicode"), + pytest.param( + Path("examples/playbooks/nomatchestest.yml"), + [], + 0, + id="nomatchestest", + ), + pytest.param(Path("examples/playbooks/unicode.yml"), [], 1, id="unicode"), pytest.param( LOTS_OF_WARNINGS_PLAYBOOK, [LOTS_OF_WARNINGS_PLAYBOOK], 0, id="lots_of_warnings", ), - pytest.param("examples/playbooks/become.yml", [], 0, id="become"), + pytest.param(Path("examples/playbooks/become.yml"), [], 0, id="become"), pytest.param( - "examples/playbooks/contains_secrets.yml", + Path("examples/playbooks/contains_secrets.yml"), [], 0, id="contains_secrets", @@ -60,7 +62,7 @@ ) def test_runner( default_rules_collection: RulesCollection, - playbook: str, + playbook: Path, exclude: list[str], length: int, ) -> None: @@ -115,7 +117,7 @@ def test_runner_unicode_format( formatter_cls: type[formatters.BaseFormatter[Any]], ) -> None: """Check that all formatters are unicode-friendly.""" - formatter = formatter_cls(os.getcwd(), display_relative_path=True) + formatter = formatter_cls(Path.cwd(), display_relative_path=True) runner = Runner( Lintable("examples/playbooks/unicode.yml", kind="playbook"), rules=default_rules_collection, @@ -129,13 +131,13 @@ def test_runner_unicode_format( @pytest.mark.parametrize( "directory_name", ( - pytest.param("test/fixtures/verbosity-tests", id="rel"), - pytest.param(os.path.abspath("test/fixtures/verbosity-tests"), id="abs"), + pytest.param(Path("test/fixtures/verbosity-tests"), id="rel"), + pytest.param(Path("test/fixtures/verbosity-tests").resolve(), id="abs"), ), ) def test_runner_with_directory( default_rules_collection: RulesCollection, - directory_name: str, + directory_name: Path, ) -> None: """Check that runner detects a directory as role.""" runner = Runner(directory_name, rules=default_rules_collection) @@ -148,7 +150,7 @@ def test_files_not_scanned_twice(default_rules_collection: RulesCollection) -> N """Ensure that lintables aren't double-checked.""" checked_files: set[Lintable] = set() - filename = os.path.abspath("examples/playbooks/common-include-1.yml") + filename = Path("examples/playbooks/common-include-1.yml").resolve() runner = Runner( filename, rules=default_rules_collection, @@ -159,9 +161,9 @@ def test_files_not_scanned_twice(default_rules_collection: RulesCollection) -> N assert len(runner.checked_files) == 2 assert len(run1) == 1 - filename = os.path.abspath("examples/playbooks/common-include-2.yml") + filename = Path("examples/playbooks/common-include-2.yml").resolve() runner = Runner( - filename, + str(filename), rules=default_rules_collection, verbosity=0, checked_files=checked_files, diff --git a/test/test_schemas.py b/test/test_schemas.py index c7dd59e1a4..639224180c 100644 --- a/test/test_schemas.py +++ b/test/test_schemas.py @@ -1,20 +1,24 @@ """Test schemas modules.""" import json import logging -import os import subprocess import sys import urllib +from pathlib import Path from time import sleep from typing import Any from unittest.mock import DEFAULT, MagicMock, patch import pytest -from spdx.config import __file__ as spdx_config_path +import spdx.config from ansiblelint.file_utils import Lintable -from ansiblelint.schemas import __file__ as schema_path -from ansiblelint.schemas import refresh_schemas, validate_file_schema +from ansiblelint.schemas import __file__ as schema_module +from ansiblelint.schemas.__main__ import refresh_schemas +from ansiblelint.schemas.main import validate_file_schema + +schema_path = Path(schema_module).parent +spdx_config_path = Path(spdx.config.__file__).parent def test_refresh_schemas() -> None: @@ -82,29 +86,22 @@ def test_validate_file_schema() -> None: def test_spdx() -> None: """Test that SPDX license identifiers are in sync.""" - _base_dir = os.path.dirname(spdx_config_path) - _licenses = os.path.join(_base_dir, "licenses.json") + _licenses = spdx_config_path / "licenses.json" license_ids = set() - with open(_licenses, encoding="utf-8") as license_fh: + with _licenses.open(encoding="utf-8") as license_fh: licenses = json.load(license_fh) for lic in licenses["licenses"]: if lic.get("isDeprecatedLicenseId"): continue license_ids.add(lic["licenseId"]) - with open( - os.path.join(os.path.dirname(schema_path), "galaxy.json"), - encoding="utf-8", - ) as f: + galaxy_json = schema_path / "galaxy.json" + with galaxy_json.open(encoding="utf-8") as f: schema = json.load(f) spx_enum = schema["$defs"]["SPDXLicenseEnum"]["enum"] if set(spx_enum) != license_ids: - with open( - os.path.join(os.path.dirname(schema_path), "galaxy.json"), - "w", - encoding="utf-8", - ) as f: + with galaxy_json.open("w", encoding="utf-8") as f: schema["$defs"]["SPDXLicenseEnum"]["enum"] = sorted(license_ids) json.dump(schema, f, indent=2) pytest.fail( diff --git a/test/test_transformer.py b/test/test_transformer.py index 5c5d08271f..9dd9a76f81 100644 --- a/test/test_transformer.py +++ b/test/test_transformer.py @@ -2,8 +2,8 @@ from __future__ import annotations import os -import pathlib import shutil +from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -22,18 +22,18 @@ @pytest.fixture(name="copy_examples_dir") def fixture_copy_examples_dir( - tmp_path: pathlib.Path, + tmp_path: Path, config_options: Namespace, -) -> Iterator[tuple[pathlib.Path, pathlib.Path]]: +) -> Iterator[tuple[Path, Path]]: """Fixture that copies the examples/ dir into a tmpdir.""" - examples_dir = pathlib.Path("examples") + examples_dir = Path("examples") shutil.copytree(examples_dir, tmp_path / "examples") - old_cwd = os.getcwd() + old_cwd = Path.cwd() try: os.chdir(tmp_path) config_options.cwd = tmp_path - yield pathlib.Path(old_cwd), tmp_path + yield old_cwd, tmp_path finally: os.chdir(old_cwd) @@ -87,7 +87,7 @@ def fixture_runner_result( ) def test_transformer( # pylint: disable=too-many-arguments, too-many-locals config_options: Options, - copy_examples_dir: tuple[pathlib.Path, pathlib.Path], + copy_examples_dir: tuple[Path, Path], playbook: str, runner_result: LintResult, transformed: bool, diff --git a/test/test_verbosity.py b/test/test_verbosity.py index 8c28966bde..d3ddb3c20b 100644 --- a/test/test_verbosity.py +++ b/test/test_verbosity.py @@ -1,7 +1,6 @@ """Tests related to our logging/verbosity setup.""" from __future__ import annotations -import os from pathlib import Path import pytest @@ -69,20 +68,20 @@ ), ), ) -def test_verbosity(verbosity: str, substrs: list[tuple[str, bool]]) -> None: +def test_verbosity( + verbosity: str, + substrs: list[tuple[str, bool]], + project_path: Path, +) -> None: """Checks that our default verbosity displays (only) warnings.""" # Piggyback off the .yamllint in the root of the repo, just for testing. # We'll "override" it with the one in the fixture, to produce a warning. - cwd = os.path.realpath( - os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."), - ) - - fakerole = os.path.join("test", "fixtures", "verbosity-tests") + fakerole = Path() / "test" / "fixtures" / "verbosity-tests" if verbosity: - result = run_ansible_lint(verbosity, fakerole, cwd=Path(cwd)) + result = run_ansible_lint(verbosity, str(fakerole), cwd=project_path) else: - result = run_ansible_lint(fakerole, cwd=Path(cwd)) + result = run_ansible_lint(str(fakerole), cwd=project_path) for substr, invert in substrs: if invert: