diff --git a/pyproject.toml b/pyproject.toml index 5066eeea9..1092b36f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ test = [ "types-paramiko>=2.7,<4", "types-python-dateutil>2,<3", "types-PyYAML>6,<7", + "pyinfra-testgen==0.1.1", ] docs = [ diff --git a/tests/test_facts.py b/tests/test_facts.py index 2fe1f3f42..84099741d 100644 --- a/tests/test_facts.py +++ b/tests/test_facts.py @@ -4,12 +4,14 @@ from os import listdir, path from unittest import TestCase +from testgen import TestGenerator + from pyinfra.api import StringCommand from pyinfra.api.facts import ShortFactBase from pyinfra.context import ctx_host, ctx_state from pyinfra_cli.util import json_encode -from .util import FakeState, YamlTest, create_host, get_command_string +from .util import FakeState, create_host, get_command_string # show full diff on json TestCase.maxDiff = None @@ -32,16 +34,19 @@ def make_fact_tests(folder_name): module = import_module("pyinfra.facts.{0}".format(module_name)) fact = getattr(module, fact_name)() - class TestTests(TestCase, metaclass=YamlTest): - yaml_test_dir = path.join("tests", "facts", folder_name) - yaml_test_prefix = "test_{0}_".format(fact.name) - + class TestTests( + TestCase, + metaclass=TestGenerator, + tests_dir=path.join("tests", "facts", folder_name), + test_prefix="test_{0}_".format(fact.name), + test_method="_test", + ): @classmethod def setUpClass(cls): # Create a global fake state that attach to context state cls.state = FakeState() - def yaml_test_function(self, test_name, test_data, fact=fact): + def _test(self, test_name, test_data, fact=fact): host = create_host(self.state, facts=test_data.get("facts", {})) with ctx_state.use(self.state): with ctx_host.use(host): diff --git a/tests/test_operations.py b/tests/test_operations.py index 2bd677eff..7abecbfab 100644 --- a/tests/test_operations.py +++ b/tests/test_operations.py @@ -6,11 +6,13 @@ from unittest import TestCase from unittest.mock import patch +from testgen import TestGenerator + from pyinfra.api import FileDownloadCommand, FileUploadCommand, FunctionCommand, StringCommand from pyinfra.context import ctx_host, ctx_state from pyinfra_cli.util import json_encode -from .util import FakeState, YamlTest, create_host, get_command_string, parse_value, patch_files +from .util import FakeState, create_host, get_command_string, parse_value, patch_files PLATFORM_NAME = platform.system() @@ -92,16 +94,19 @@ def make_operation_tests(arg): # Generate a test class @patch("pyinfra.operations.files.get_timestamp", lambda: "a-timestamp") @patch("pyinfra.operations.util.files.get_timestamp", lambda: "a-timestamp") - class TestTests(TestCase, metaclass=YamlTest): - yaml_test_dir = path.join("tests", "operations", arg) - yaml_test_prefix = "test_{0}_{1}_".format(module_name, op_name) - + class TestTests( + TestCase, + metaclass=TestGenerator, + tests_dir=path.join("tests", "operations", arg), + test_prefix="test_{0}_{1}_".format(module_name, op_name), + test_method="_test", + ): @classmethod def setUpClass(cls): # Create a global fake state that attach to context state cls.state = FakeState() - def yaml_test_function(self, test_name, test_data): + def _test(self, test_name, test_data): if ( "require_platform" in test_data and PLATFORM_NAME not in test_data["require_platform"] diff --git a/tests/util.py b/tests/util.py index 9b60a3926..d643f4d24 100644 --- a/tests/util.py +++ b/tests/util.py @@ -8,8 +8,6 @@ from pathlib import Path from unittest.mock import patch -import yaml - from pyinfra.api import Config, Inventory from pyinfra.api.util import get_kwargs_str @@ -479,30 +477,3 @@ def create_host(state, name=None, facts=None, data=None): real_facts[name] = fact_data return FakeHost(state, name, facts=real_facts, data=data) - - -class YamlTest(type): - def __new__(cls, name, bases, attrs): - test_suffixes = {".yaml", ".yml", ".json"} - - tests_dir = Path(attrs["yaml_test_dir"]) - - test_files = [f for f in tests_dir.iterdir() if f.suffix in test_suffixes] - - test_prefix = attrs.get("yaml_test_prefix", "test_") - - def gen_test(test_name, test_file): - def test(self): - test_data = yaml.safe_load(test_file.open(encoding="utf-8").read()) - self.yaml_test_function(test_name, test_data) - - return test - - # Loop them and create class methods to call the yaml_test_function - for test_file in test_files: - test_name = test_file.stem - # Attach the method - method_name = "{0}{1}".format(test_prefix, test_name) - attrs[method_name] = gen_test(test_name, test_file) - - return type.__new__(cls, name, bases, attrs) diff --git a/uv.lock b/uv.lock index fe4914236..4eea10cfe 100644 --- a/uv.lock +++ b/uv.lock @@ -1183,6 +1183,7 @@ dev = [ { name = "mypy" }, { name = "myst-parser", marker = "python_full_version >= '3.13'" }, { name = "pyinfra-guzzle-sphinx-theme", marker = "python_full_version >= '3.13'" }, + { name = "pyinfra-testgen" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-testinfra" }, @@ -1208,6 +1209,7 @@ test = [ { name = "flake8-isort" }, { name = "isort" }, { name = "mypy" }, + { name = "pyinfra-testgen" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-testinfra" }, @@ -1247,6 +1249,7 @@ dev = [ { name = "mypy", specifier = "==1.17.1" }, { name = "myst-parser", marker = "python_full_version >= '3.13'", specifier = ">=4.0.1,<5" }, { name = "pyinfra-guzzle-sphinx-theme", marker = "python_full_version >= '3.13'", specifier = ">=0.17" }, + { name = "pyinfra-testgen", specifier = "==0.1.1" }, { name = "pytest", specifier = ">=8.3.5,<9" }, { name = "pytest-cov", specifier = ">=7.0.0,<8" }, { name = "pytest-testinfra", specifier = ">=10.2.2,<11" }, @@ -1272,6 +1275,7 @@ test = [ { name = "flake8-isort", specifier = "==6.1.2,<7" }, { name = "isort", specifier = ">=6.0.1,<7" }, { name = "mypy", specifier = "==1.17.1" }, + { name = "pyinfra-testgen", specifier = "==0.1.1" }, { name = "pytest", specifier = ">=8.3.5,<9" }, { name = "pytest-cov", specifier = ">=7.0.0,<8" }, { name = "pytest-testinfra", specifier = ">=10.2.2,<11" }, @@ -1291,6 +1295,18 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6c/3e/64b73fdea51efa09947fe3b15556213ab083402797192efd1422fd63964d/pyinfra-guzzle_sphinx_theme-0.17.tar.gz", hash = "sha256:db518ee80a62fb2b8770c750ca9b57243821ce57c38b5ec13ca3fa3430ced439", size = 301013, upload-time = "2025-04-07T20:40:56.214Z" } +[[package]] +name = "pyinfra-testgen" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/ad/4d7ae773502ceae3ac3a7f4ea6232f865e28a9766931db2af25ab369449c/pyinfra_testgen-0.1.1.tar.gz", hash = "sha256:739a59d127d15c2e74bc965f9e77f41d080899fee18b6a10e7f5ca3153739697", size = 1065, upload-time = "2025-09-01T20:57:39.844Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/9a/edea57e31074b1a504b1b5b20180410971b393669e5388e773a4e49f4563/pyinfra_testgen-0.1.1-py3-none-any.whl", hash = "sha256:23d71cbe666df7a0b12a000a5cbdaade932620faced2c89bffc8a066153c9303", size = 1820, upload-time = "2025-09-01T20:57:38.749Z" }, +] + [[package]] name = "pynacl" version = "1.6.0"