Source code for testsuite.plugins.common

import json
import pathlib
import typing
import warnings

import pytest

from testsuite import annotations
from testsuite._internal import fixture_class
from testsuite.utils import cached_property
from testsuite.utils import json_util
from testsuite.utils import yaml_util


class BaseError(Exception):
    """Base class for errors from this module."""


class UnsupportedFileModeError(BaseError):
    """Unsupported file open mode passed."""


class LoadJsonError(BaseError):
    """Json file load or parse failure error."""


class LoadYamlError(BaseError):
    """Yaml file load or parse failure error."""


[docs]class GetSearchPathesFixture(fixture_class.Fixture): """Generates sequence of pathes for static files.""" _fixture__search_directories_existing: typing.Tuple[pathlib.Path, ...] _fixture__path_entries_cache: typing.Callable
[docs] def __call__( self, filename: annotations.PathOrStr, ) -> typing.Iterator[pathlib.Path]: for directory in self._fixture__search_directories_existing: entry = self._fixture__path_entries_cache(directory, filename) if entry.exists(): yield entry
class SearchPathFixture(fixture_class.Fixture): _fixture_get_search_pathes: GetSearchPathesFixture def __call__( self, filename: annotations.PathOrStr, directory: bool = False, ) -> typing.Iterator[pathlib.Path]: for abs_filename in self._fixture_get_search_pathes(filename): if directory: if abs_filename.is_dir(): yield abs_filename else: if abs_filename.is_file(): yield abs_filename
[docs]class GetFilePathFixture(fixture_class.Fixture): """Returns path to static regular file.""" _fixture_search_path: SearchPathFixture _fixture_get_search_pathes: GetSearchPathesFixture _fixture__search_directories_existing: typing.Tuple[pathlib.Path, ...]
[docs] def __call__( self, filename: annotations.PathOrStr, *, missing_ok=False, ) -> typing.Optional[pathlib.Path]: for path in self._fixture_search_path(filename): return path if missing_ok: return None __tracebackhide__ = True raise self._file_not_found_error( f'File {filename} was not found', filename, )
def _file_not_found_error(self, message, filename): pathes = '\n'.join( f' - {path / filename}' for path in self._fixture__search_directories_existing ) return FileNotFoundError( f'{message}\n\nThe following pathes were examined:\n{pathes}', )
[docs]class GetDirectoryPathFixture(GetFilePathFixture): """Returns path to static directory."""
[docs] def __call__( self, filename: annotations.PathOrStr, *, missing_ok=False, ) -> typing.Optional[pathlib.Path]: for path in self._fixture_search_path(filename, directory=True): return path if missing_ok: return None __tracebackhide__ = True raise self._file_not_found_error( f'Directory {filename} was not found', filename, )
[docs]class OpenFileFixture(fixture_class.Fixture): """Open static file by name. Only read-only open modes are supported. Example: .. code-block:: python def test_foo(open_file): with open_file('foo') as fp: ... """ _modes_whitelist = frozenset(['r', 'rt', 'rb']) _fixture_get_file_path: GetFilePathFixture
[docs] def __call__( self, filename: annotations.PathOrStr, mode='r', buffering=-1, encoding='utf-8', errors=None, ) -> typing.IO: if mode not in self._modes_whitelist: __tracebackhide__ = True raise UnsupportedFileModeError( f'Incorrect file open mode {mode!r} passed. ' f'Only read-only modes are supported.', ) return open( self._fixture_get_file_path(filename), mode=mode, buffering=buffering, encoding=encoding, errors=errors, )
[docs]class LoadFixture(fixture_class.Fixture): """Load file from static directory. Example: .. code-block:: python def test_something(load): data = load('filename') :return: :py:class:`LoadFixture` callable instance. """ _fixture_get_file_path: GetFilePathFixture
[docs] def __call__( self, filename: annotations.PathOrStr, encoding='utf-8', errors=None, *, missing_ok=False, ) -> typing.Union[bytes, str]: """Load static text file. :param filename: static file name part. :param encoding: stream encoding, see :func:`open`. :param errors: error handling mode see :func:`open`. :returns: ``str`` instance. """ path = self._fixture_get_file_path(filename, missing_ok=missing_ok) if path is None: return None return path.read_text(encoding=encoding, errors=errors)
[docs]class LoadBinaryFixture(fixture_class.Fixture): """Load binary data from static directory. Example: .. code-block:: python def test_something(load_binary): bytes_data = load_binary('data.bin') """ _fixture_get_file_path: GetFilePathFixture
[docs] def __call__(self, filename: annotations.PathOrStr) -> bytes: """Load static binary file. :param filename": static file name part :returns: ``bytes`` file content. """ path = self._fixture_get_file_path(filename) return path.read_bytes()
class JsonLoadsFixture(fixture_class.Fixture): """Load json doc from string. Json loader runs ``json_util.loads(data, ..., *args, **kwargs)`` hooks. It does: * bson.json_util.object_hook() * mockserver substitution Example: .. code-block:: python def test_something(json_loads): json_obj = json_loads('{"key": "value"}') """ _fixture_load_json_defaults: typing.Dict _fixture_object_hook: typing.Any def __call__(self, content, *args, **kwargs) -> typing.Any: if 'object_hook' not in kwargs: kwargs['object_hook'] = self._fixture_object_hook return json_util.loads( content, *args, **self._fixture_load_json_defaults, **kwargs, )
[docs]class LoadJsonFixture(fixture_class.Fixture): """Load json doc from static directory. Json loader runs ``json_util.loads(data, ..., *args, **kwargs)`` hooks. It does: * bson.json_util.object_hook() * mockserver substitution Example: .. code-block:: python def test_something(load_json): json_obj = load_json('filename.json') """ _fixture_load: LoadFixture _fixture_json_loads: JsonLoadsFixture
[docs] def __call__( self, filename: annotations.PathOrStr, *args, missing_ok=False, missing=None, **kwargs, ) -> typing.Any: content = self._fixture_load(filename, missing_ok=missing_ok) if content is None: return missing try: return self._fixture_json_loads(content, *args, **kwargs) except json.JSONDecodeError as err: __tracebackhide__ = True raise LoadJsonError( f'Failed to load JSON file {filename}', ) from err
[docs]class LoadYamlFixture(fixture_class.Fixture): """Load yaml doc from static directory. .. code-block:: python def test_something(load_yaml): yaml_obj = load_yaml('filename.yaml') """ _fixture_load: LoadFixture
[docs] def __call__( self, filename: annotations.PathOrStr, *args, **kwargs, ) -> typing.Any: content = self._fixture_load(filename) try: return yaml_util.load(content, *args, **kwargs) except yaml_util.ParserError as exc: __tracebackhide__ = True raise LoadYamlError( f'Failed to load YAML file {filename}', ) from exc
FilePathsCache = typing.Dict[pathlib.Path, typing.List[pathlib.Path]] get_search_pathes = fixture_class.create_fixture_factory( GetSearchPathesFixture, ) search_path = fixture_class.create_fixture_factory(SearchPathFixture) get_file_path = fixture_class.create_fixture_factory(GetFilePathFixture) get_directory_path = fixture_class.create_fixture_factory( GetDirectoryPathFixture, ) open_file = fixture_class.create_fixture_factory(OpenFileFixture) load = fixture_class.create_fixture_factory(LoadFixture) load_binary = fixture_class.create_fixture_factory(LoadBinaryFixture) json_loads = fixture_class.create_fixture_factory(JsonLoadsFixture) load_json = fixture_class.create_fixture_factory(LoadJsonFixture) load_yaml = fixture_class.create_fixture_factory(LoadYamlFixture) def pytest_configure(config): config.addinivalue_line( 'markers', 'nofilldb: test does not need db initialization', )
[docs]@pytest.fixture def static_dir(testsuite_request_directory) -> pathlib.Path: """Static directory related to test path. Returns static directory relative to test file, e.g.:: |- tests/ |- static/ <-- base static directory for test_foo.py |- test_foo.py """ return testsuite_request_directory / 'static'
[docs]@pytest.fixture def initial_data_path() -> typing.Tuple[pathlib.Path, ...]: """Use this fixture to override base static search path. .. code-block:: python @pytest.fixture def initial_data_path(): return ( pathlib.Path(PROJECT_ROOT) / 'tests/static', pathlib.Path(PROJECT_ROOT) / 'static', ) """ return ()
@pytest.fixture def get_all_static_file_paths( static_dir: pathlib.Path, _file_paths_cache: FilePathsCache, ): def _get_file_paths() -> typing.List[pathlib.Path]: if static_dir not in _file_paths_cache: _file_paths_cache[static_dir] = [ path for path in static_dir.rglob('') if path.is_file ] return _file_paths_cache[static_dir] return _get_file_paths @pytest.fixture def object_substitute(object_hook): """Perform object substitution as in load_json.""" def _substitute(content, *args, **kwargs): return json_util.substitute( content, object_hook=object_hook, *args, **kwargs, ) return _substitute
[docs]@pytest.fixture(scope='session') def testsuite_get_source_path(): def get_source_path(path) -> pathlib.Path: return pathlib.Path(path) return get_source_path
[docs]@pytest.fixture(scope='session') def testsuite_get_source_directory(testsuite_get_source_path): def get_source_directory(path) -> pathlib.Path: return testsuite_get_source_path(path).parent return get_source_directory
@pytest.fixture def testsuite_request_path(request, testsuite_get_source_path) -> pathlib.Path: return testsuite_get_source_path(request.module.__file__) @pytest.fixture def testsuite_request_directory(testsuite_request_path) -> pathlib.Path: return testsuite_request_path.parent @pytest.fixture(scope='session') def worker_id(request) -> str: if hasattr(request.config, 'workerinput'): return request.config.workerinput['workerid'] return 'master' @pytest.fixture(scope='session') def _file_paths_cache() -> FilePathsCache: return {} @pytest.fixture def _search_directories( request, static_dir: pathlib.Path, initial_data_path: typing.Tuple[pathlib.Path, ...], testsuite_request_path, _path_entries_cache, ) -> typing.Tuple[pathlib.Path, ...]: test_module_name = testsuite_request_path.stem node_name = request.node.name if '[' in node_name: node_name = node_name[: node_name.index('[')] search_directories = [ _path_entries_cache(static_dir, test_module_name, node_name), _path_entries_cache(static_dir, test_module_name), _path_entries_cache(static_dir, 'default'), _path_entries_cache(static_dir, ''), ] search_directories.extend( _path_entries_cache(path) for path in initial_data_path ) return tuple(search_directories) @pytest.fixture def _search_directories_existing(_search_directories): return tuple(path for path in _search_directories if path.is_dir()) @pytest.fixture(scope='session') def load_json_defaults(): return {} @pytest.fixture(scope='session') def _cached_stat_path(): path_type = type(pathlib.Path()) stat_cache = {} glob_cache = {} content_cache = {} iterdir_cache = {} class CachedStatPath(path_type): def stat(self, *, follow_symlinks=True): key = str(self) if key in stat_cache: is_exc, value = stat_cache[key] if is_exc: raise value return value try: value = super().stat() stat_cache[key] = (False, value) return value except FileNotFoundError as exc: stat_cache[key] = (True, exc) raise def iterdir(self): cache_key = str(self) data = iterdir_cache.get(cache_key) if data is None: data = tuple(super().iterdir()) content_cache[cache_key] = data return data def read_bytes(self): cache_key = (str(self), 'b') data = content_cache.get(cache_key) if data is None: data = super().read_bytes() content_cache[cache_key] = data return data def read_text(self, encoding=None, errors=None): cache_key = (str(self), encoding, errors) data = content_cache.get(cache_key) if data is None: data = super().read_text(encoding=encoding, errors=errors) content_cache[cache_key] = data return data def glob(self, pattern): key = (str(self), pattern) if key not in glob_cache: data = glob_cache[key] = tuple(super().glob(pattern)) return data return glob_cache[key] def rglob(self, pattern): key = ('r', str(self), pattern) if key not in glob_cache: data = glob_cache[key] = tuple(super().rglob(pattern)) return data return glob_cache[key] def exists(self): return self._exists def is_dir(self): return self._is_dir def is_file(self): return self._is_file @cached_property def _is_dir(self): return super().is_dir() @cached_property def _is_file(self): return super().is_file() @cached_property def _exists(self): return super().exists() return CachedStatPath @pytest.fixture(scope='session') def _path_entries_cache(_cached_stat_path): entries_cache = {} def get(*parts): result = entries_cache.get(parts) if result: return result result = _cached_stat_path(*parts) entries_cache[parts] = result return result return get