190 lines
6.2 KiB
Python
190 lines
6.2 KiB
Python
import shutil
|
|
from abc import ABC, abstractmethod
|
|
from enum import Enum
|
|
from pathlib import Path
|
|
from typing import Any, Dict
|
|
|
|
import pytest
|
|
|
|
from functional2.testlib.fixtures.formatter import BalancedTemplater
|
|
|
|
|
|
class Fileish(ABC):
|
|
"""
|
|
Baseclass, which allows files to be copied declaratively
|
|
"""
|
|
|
|
@abstractmethod
|
|
def copy_to(self, path: Path, origin: Path) -> None:
|
|
"""
|
|
Copies this file to the given TempDir
|
|
:param path: TempDir for the test
|
|
:param origin: Directory the tests originates in. Used to adjust relative paths
|
|
"""
|
|
pass
|
|
|
|
|
|
class _ByContentFileish(Fileish, ABC):
|
|
def __init__(self, mode: int | None = None):
|
|
self.mode = mode
|
|
|
|
@abstractmethod
|
|
def get_content(self, origin: Path) -> str:
|
|
"""
|
|
Returns the content, which should be present in the current path
|
|
:return: content as a string
|
|
"""
|
|
pass
|
|
|
|
def copy_to(self, path: Path, origin: Path) -> None:
|
|
path.write_text(self.get_content(origin))
|
|
if self.mode is not None:
|
|
path.chmod(self.mode)
|
|
|
|
|
|
class File(_ByContentFileish):
|
|
def __init__(self, file_contents: str, mode: int | None = None):
|
|
"""
|
|
Declares a file by its content
|
|
:param file_contents: content of the file as a string
|
|
:param mode: Optionally change the mode of the file (e.g. to executable)
|
|
"""
|
|
super().__init__(mode)
|
|
self.file_contents = file_contents
|
|
|
|
def get_content(self, _: Path) -> str:
|
|
return self.file_contents
|
|
|
|
|
|
class CopyFile(Fileish):
|
|
def __init__(self, source: str):
|
|
"""
|
|
Declares a file as a copy of an existing file
|
|
:param source: Path to the file to be copied
|
|
"""
|
|
self.source = source
|
|
|
|
def copy_to(self, path: Path, origin: Path):
|
|
shutil.copyfile(origin / self.source, path)
|
|
|
|
|
|
class CopyTree(Fileish):
|
|
def __init__(self, tree_base: str):
|
|
"""
|
|
Declares a folder as a copy of an existing folder
|
|
:param tree_base: base folder of the tree being copied
|
|
"""
|
|
self.tree_base = tree_base
|
|
|
|
def copy_to(self, path: Path, origin: Path):
|
|
shutil.copytree(origin / self.tree_base, path, dirs_exist_ok=True)
|
|
|
|
|
|
class CopyTemplate(_ByContentFileish):
|
|
def __init__(self, template: str, values: Dict[str, Any], mode: int | None = None):
|
|
"""
|
|
Declares a file as an initiated version of the given file template
|
|
:param template: source template's file name. Parameters formatted as `{key_name}` are replaced by corresponding values
|
|
:param values: dictionary of key_name and value to be replaced in the template.
|
|
:param mode: Optionally change the mode of the file (e.g. to executable)
|
|
"""
|
|
self.template = template
|
|
self.values = values
|
|
self.content: str | None = None
|
|
|
|
super().__init__(mode)
|
|
|
|
def get_content(self, origin: Path) -> str:
|
|
template_path = origin / self.template
|
|
template_content = template_path.read_text()
|
|
|
|
self.content = BalancedTemplater(template_content).substitute(**self.values)
|
|
|
|
return self.content
|
|
|
|
|
|
class RelativeTo(str, Enum):
|
|
TEST = "test"
|
|
"""
|
|
Base for the given path is the directory of the test
|
|
"""
|
|
TARGET = "target"
|
|
"""
|
|
Base for the given path is the directory of the target / where all files are created
|
|
"""
|
|
ROOT = "root"
|
|
"""
|
|
Base for the given path is the root folder (/)
|
|
"""
|
|
SELF = "self"
|
|
"""
|
|
No base path is given, used to create relative symlinks (e.g. `"../test"`)
|
|
"""
|
|
|
|
|
|
class Symlink(Fileish):
|
|
def __init__(self, source: str, relative_to: RelativeTo = RelativeTo.TARGET):
|
|
"""
|
|
Declares a file as a symlink to a different location
|
|
|
|
NOTE: due to limitations of symlinks on Windows, tests using this might be flakey and fail!!
|
|
:param source: Path to the source where the symlink should be pointing
|
|
:raise ValueError: When the given source is an absolute Path, but relative to wasn't set to ROOT
|
|
"""
|
|
self.source = source
|
|
self.relative_to = relative_to
|
|
|
|
def copy_to(self, path: Path, origin: Path):
|
|
if self.relative_to is not RelativeTo.ROOT and Path(self.source).is_absolute():
|
|
msg = "absolute paths are only supported when using RelativeTo.ROOT"
|
|
raise ValueError(msg)
|
|
match self.relative_to:
|
|
case RelativeTo.TEST:
|
|
base = origin
|
|
case RelativeTo.TARGET:
|
|
base = path.parent
|
|
case RelativeTo.ROOT:
|
|
base = Path("/")
|
|
case RelativeTo.SELF:
|
|
base = None
|
|
case _:
|
|
msg = f"unknown relativity {self.relative_to}"
|
|
raise ValueError(msg)
|
|
target_path = base / self.source if base is not None else Path(self.source)
|
|
path.symlink_to(target_path)
|
|
|
|
|
|
type FileDeclaration = Dict[str, Fileish | "FileDeclaration"]
|
|
|
|
|
|
def _init_files(files: FileDeclaration, tmp_path: Path, request: pytest.FixtureRequest) -> None:
|
|
"""
|
|
This internal function is needed because one cannot call a fixture directly since pytest 4.0
|
|
"""
|
|
|
|
for name, definition in files.items():
|
|
destination = tmp_path / name
|
|
if isinstance(definition, Fileish):
|
|
definition.copy_to(destination, request.path.parent)
|
|
else:
|
|
# Initialize subdirectory
|
|
destination.mkdir()
|
|
_init_files(definition, destination, request)
|
|
|
|
|
|
@pytest.fixture
|
|
def files(tmp_path: Path, request: pytest.FixtureRequest) -> Path:
|
|
"""
|
|
Initializes the given files into the TempDir of the test.
|
|
This ensures all necessary files and only those are present
|
|
To use this add `@pytest.mark.parametrize("files", [list_of_your_files_to_test, more_files_to_test], indirect=True)` above your test.
|
|
The test is run once for each of the sets of files provided as the second argument.
|
|
Each Set of files should be of the :py:type:`FileDeclaration` type
|
|
|
|
:param tmp_path: TempDir of the test
|
|
:param request: Fixture information provided by pytest, used to parametrize the files
|
|
:return: Path to where the files were created
|
|
"""
|
|
_init_files(request.param, tmp_path, request)
|
|
return tmp_path
|