diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index c2cd6a4322..d88091506b 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -51,7 +51,7 @@ jobs: matrix: ${{ fromJson(needs.pre.outputs.matrix) }} env: - PYTEST_REQPASS: 447 + PYTEST_REQPASS: 449 steps: - uses: actions/checkout@v3 with: diff --git a/.gitignore b/.gitignore index e7b1ca77ca..2e88e56ed3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ tools/test-schema/node_modules site .DS_Store src/molecule/_version.py -venv +.vscode +src/molecule/test/resources/.extensions/ diff --git a/src/molecule/command/base.py b/src/molecule/command/base.py index 5089096041..514ab8b860 100644 --- a/src/molecule/command/base.py +++ b/src/molecule/command/base.py @@ -21,9 +21,11 @@ import abc import collections +import contextlib import logging import os import shutil +import subprocess from typing import Any, Callable import click @@ -173,6 +175,29 @@ def execute_scenario(scenario): scenario._remove_scenario_state_directory() +def filter_ignored_scenarios(scenario_paths) -> list[str]: + command = ["git", "check-ignore", *scenario_paths] + + with contextlib.suppress(subprocess.CalledProcessError, FileNotFoundError): + proc = subprocess.run( + args=command, + capture_output=True, + check=True, + text=True, + shell=False, + ) + + try: + ignored = proc.stdout.splitlines() + paths = [ + candidate for candidate in scenario_paths if str(candidate) not in ignored + ] + except NameError: + paths = scenario_paths + + return paths + + def get_configs(args, command_args, ansible_args=(), glob_str=MOLECULE_GLOB): """Glob the current directory for Molecule config files, instantiate config \ objects, and returns a list. @@ -184,6 +209,14 @@ def get_configs(args, command_args, ansible_args=(), glob_str=MOLECULE_GLOB): `ansible-playbook` command. :return: list """ + scenario_paths = glob.glob( + glob_str, + flags=wcmatch.pathlib.GLOBSTAR + | wcmatch.pathlib.BRACE + | wcmatch.pathlib.DOTGLOB, + ) + + scenario_paths = filter_ignored_scenarios(scenario_paths) configs = [ config.Config( molecule_file=util.abs_path(c), @@ -191,12 +224,7 @@ def get_configs(args, command_args, ansible_args=(), glob_str=MOLECULE_GLOB): command_args=command_args, ansible_args=ansible_args, ) - for c in glob.glob( - glob_str, - flags=wcmatch.pathlib.GLOBSTAR - | wcmatch.pathlib.BRACE - | wcmatch.pathlib.DOTGLOB, - ) + for c in scenario_paths ] _verify_configs(configs, glob_str) diff --git a/src/molecule/test/b_functional/test_command.py b/src/molecule/test/b_functional/test_command.py index 8e86d87c86..05f8c68dac 100644 --- a/src/molecule/test/b_functional/test_command.py +++ b/src/molecule/test/b_functional/test_command.py @@ -20,10 +20,12 @@ from __future__ import annotations import os +import pathlib import pytest from pytest import FixtureRequest +from molecule.command import base from molecule.test.b_functional.conftest import ( idempotence, init_scenario, @@ -336,6 +338,47 @@ def test_sample_collection() -> None: ) +@pytest.mark.parametrize( + ("scenario_name"), + [ + ("test_w_gitignore"), + ("test_wo_gitignore"), + ], +) +def test_with_and_without_gitignore( + monkeypatch: pytest.MonkeyPatch, + scenario_name: str, +) -> None: + if scenario_name == "test_wo_gitignore": + + def mock_return(scenario_paths) -> list[str]: + return scenario_paths + + monkeypatch.setattr( + "molecule.command.base.filter_ignored_scenarios", + mock_return, + ) + + resource_path = pathlib.Path(__file__).parent.parent / "resources" + + monkeypatch.chdir(resource_path) + + pathlib.Path(resource_path / f".extensions/molecule/{scenario_name}").mkdir( + parents=True, + exist_ok=True, + ) + + pathlib.Path(f".extensions/molecule/{scenario_name}/molecule.yml").touch() + + op = base.get_configs({}, {}, glob_str="**/molecule/*/molecule.yml") + + names = [config.scenario.name for config in op] + if scenario_name == "test_w_gitignore": + assert scenario_name not in names + elif scenario_name == "test_wo_gitignore": + assert scenario_name in names + + def test_podman() -> None: assert ( run_command(