mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Add test_settings_syntax.py and supporting test data files in "unpythonized" format to skirt by the RUFF/Black format/linters.
This commit is contained in:
parent
513abbfdc6
commit
b9bfb8c759
6 changed files with 716 additions and 0 deletions
156
pelican/tests/conftest.py
Normal file
156
pelican/tests/conftest.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#
|
||||
# File: conftest.py
|
||||
# Tester: pytest
|
||||
#
|
||||
import contextlib
|
||||
import locale
|
||||
import logging
|
||||
import os # os.path is being discontinued; use pathlib.Path
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import filelock
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def locale_to_c__fixture_session():
|
||||
"""Load/unload the "C" locale"""
|
||||
old_locale = locale.setlocale(locale.LC_ALL)
|
||||
locale.setlocale(locale.LC_ALL, "C")
|
||||
|
||||
yield
|
||||
|
||||
locale.setlocale(locale.LC_ALL, old_locale)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def temp_dir_all_unit_tests__fixture_session(tmp_path_factory):
|
||||
"""A temporary directory for ALL unit tests"""
|
||||
|
||||
# By supplying a basename, this insulates us from unexpected TMPDIR environment.
|
||||
yield tmp_path_factory.mktemp(basename="tmp_pelican", numbered=False)
|
||||
|
||||
shutil.rmtree(tmp_path_factory.getbasetemp())
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def temp_settings_dir_all_unit_tests__fixture_session(
|
||||
tmp_path_factory, temp_dir_all_unit_tests__fixture_session
|
||||
):
|
||||
temp_path = temp_dir_all_unit_tests__fixture_session
|
||||
session_dirname: str = str(temp_path / "tmp_settings")
|
||||
session_temp_path = tmp_path_factory.mktemp(
|
||||
basename=session_dirname, numbered=False
|
||||
)
|
||||
|
||||
yield session_temp_path
|
||||
|
||||
shutil.rmtree(session_temp_path)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def lockfile_sys_modules__fixture_session(temp_dir_all_unit_tests__fixture_session):
|
||||
"""Provide a locking file specific to `sys.modules[]` (per pytest)"""
|
||||
# to be called only by fixture_session_serialize_sys_modules()
|
||||
lock_file = temp_dir_all_unit_tests__fixture_session / "sys_modules_serial.lock"
|
||||
yield filelock.FileLock(lock_file=str(lock_file))
|
||||
with contextlib.suppress(OSError):
|
||||
os.remove(path=lock_file)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def serialize_sys_modules__fixture_session(lockfile_sys_modules__fixture_session):
|
||||
"""mark function test as serial/sequential ordering
|
||||
|
||||
Include `serial` in the function's argument list ensures
|
||||
that no other test(s) also having `serial` in its argument list
|
||||
shall run."""
|
||||
with lockfile_sys_modules__fixture_session.acquire(poll_interval=0.1):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def assert_module_integrity__fixture_session(serialize_sys_modules__fixture_session):
|
||||
"""Ensure that `sys.modules` is intact after all unit tests in this module"""
|
||||
saved_sys_modules = sys.modules
|
||||
yield
|
||||
if not (saved_sys_modules == sys.modules):
|
||||
raise AssertionError(f"Entire {__file__} failed to preserve sys.modules.")
|
||||
else:
|
||||
logging.debug("all modules accounted for in sys.modules[].")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_tests_dir__fixture_session():
|
||||
"""Get the absolute directory path of `tests` subdirectory
|
||||
|
||||
pytest session-wide fixture will provide a full directory path to the
|
||||
location of this `test_settings_syntax.py` pytest script file.
|
||||
|
||||
Note: used to assist in locating the `settings` directory underneath it,
|
||||
and to guide toward the root directory of this Pelican package.
|
||||
|
||||
This fixture gets evoked exactly once package-wide due to `scope=session`.
|
||||
|
||||
:return: Returns the absolute path of the tests directory
|
||||
:rtype: pathlib.Path"""
|
||||
abs_tests_dirpath: Path = Path(__file__).parent # secret sauce
|
||||
yield abs_tests_dirpath
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_tests_settings_dir__fixture_session(get_tests_dir__fixture_session):
|
||||
"""Get the absolute directory path of `tests/settings` subdirectory
|
||||
|
||||
This pytest session-wide fixture will provide the full directory
|
||||
path of the `settings` subdirectory containing various test configuration
|
||||
files for the `test_settings*.py` unit tests.
|
||||
|
||||
This fixture gets evoked exactly once package-wide due to `scope=session`.
|
||||
|
||||
:return: Returns the absolute path of the `tests/settings` directory
|
||||
:rtype: pathlib.Path"""
|
||||
settings_dirpath: Path = get_tests_dir__fixture_session / "settings"
|
||||
yield settings_dirpath
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_pelican_source_dir__fixture_session(
|
||||
get_tests_dir__fixture_session,
|
||||
):
|
||||
"""Get the `pelican` source subdirectory in absolute directory path.
|
||||
This fixture gets evoked exactly once package-wide due to `scope=session`.
|
||||
|
||||
:return: Returns the absolute path of the Pelican root directory
|
||||
:rtype: pathlib.Path"""
|
||||
test_abs_dirpath = get_tests_dir__fixture_session
|
||||
# Go up one directory to Pelican source directory
|
||||
pelican_source_subdir_path: Path = test_abs_dirpath.parent
|
||||
yield pelican_source_subdir_path
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def get_pelican_package_dir__fixture_session(
|
||||
get_pelican_source_dir__fixture_session,
|
||||
):
|
||||
"""Get the absolute directory path of `pelican` package root directory
|
||||
This fixture gets evoked exactly once package-wide due to `scope=session`.
|
||||
|
||||
:return: Returns the path of the Pelican root directory
|
||||
:rtype: pathlib.Path"""
|
||||
pelican_source_subdir_path = get_pelican_source_dir__fixture_session
|
||||
# Go up one directory to Pelican root directory
|
||||
pelican_package_dir_path = pelican_source_subdir_path.parent
|
||||
yield Path(pelican_package_dir_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# if executing this file alone, it tests this file alone.
|
||||
# Can execute from any current working directory
|
||||
pytest.main([__file__])
|
||||
|
||||
# more, complex variants of pytest.
|
||||
# pytest.main([__file__, "-n0", "-rAw", "--capture=no", "--no-header"])
|
||||
# pytest.main([__file__, "-n0"]) # single-process, single-thread
|
||||
18
pelican/tests/settings/pelicanconf-indent1-error.py.disabled
Normal file
18
pelican/tests/settings/pelicanconf-indent1-error.py.disabled
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#! Ohhhtay, this is not a python file despite it's .py file extension
|
||||
# Error to occur is at line 13, column 5
|
||||
# CAUTION: do not change this error location without corresponding changes in test cases
|
||||
|
||||
import me
|
||||
|
||||
def function_a(arg1):
|
||||
|
||||
value2 = arg1
|
||||
value3 = value2 + 1
|
||||
return value3
|
||||
|
||||
int i
|
||||
|
||||
for (i = 1; i < 200; ) {
|
||||
i = i + 1;
|
||||
|
||||
}
|
||||
18
pelican/tests/settings/pelicanconf-indent2-error.py.disabled
Normal file
18
pelican/tests/settings/pelicanconf-indent2-error.py.disabled
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#! Ohhhtay, this is not a python file despite it's .py file extension
|
||||
# Error to occur is at line 13, column 5
|
||||
# CAUTION: do not change this error location without corresponding changes in test cases
|
||||
|
||||
import me
|
||||
|
||||
def function_a(arg1):
|
||||
|
||||
value2 = arg1
|
||||
value3 = value2 + 1
|
||||
return value3
|
||||
|
||||
int i
|
||||
|
||||
for (i = 1; i < 200; ) {
|
||||
i = i + 1;
|
||||
|
||||
}
|
||||
18
pelican/tests/settings/pelicanconf-syntax3-error.py.disabled
Normal file
18
pelican/tests/settings/pelicanconf-syntax3-error.py.disabled
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#! Ohhhtay, this is not a python file despite it's .py file extension
|
||||
# Error to occur is at line 13, column 5
|
||||
# CAUTION: do not change this error location without corresponding changes in test cases
|
||||
|
||||
import me
|
||||
|
||||
def function_a(arg1):
|
||||
|
||||
value2 = arg1
|
||||
value3 = value2 + 1
|
||||
return value3
|
||||
|
||||
int i
|
||||
|
||||
for (i = 1; i < 200; ) {
|
||||
i = i + 1;
|
||||
|
||||
}
|
||||
12
pelican/tests/settings/pelicanconf-syntax4-error.py.disabled
Normal file
12
pelican/tests/settings/pelicanconf-syntax4-error.py.disabled
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#! Ohhhtay, this is not a python file despite it's .py file extension
|
||||
|
||||
# Error to occur is at line 5, column 2
|
||||
|
||||
// put some C code in it
|
||||
|
||||
int i
|
||||
|
||||
for (i = 1; i < 200; ) {
|
||||
i = i + 1;
|
||||
|
||||
}
|
||||
494
pelican/tests/test_settings_syntax.py
Normal file
494
pelican/tests/test_settings_syntax.py
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
#
|
||||
# Focused on settings.py/load_source(), specifically syntax error handling
|
||||
#
|
||||
# Minimum version: Python 3.6 (tempfile.mkdtemp())
|
||||
# Minimum version: Pytest 4.0, Python 3.8+
|
||||
#
|
||||
# To see collection/ordering of a fixture for a specific function, execute:
|
||||
#
|
||||
# pytest -n0 --setup-plan \
|
||||
# test_settings_syntax.py::TestSettingsSyntax::test_load_source_syntax_indent
|
||||
|
||||
|
||||
import errno
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from pelican.settings import (
|
||||
load_source,
|
||||
)
|
||||
|
||||
TMP_DIRNAME_SUFFIX = "pelican"
|
||||
|
||||
# Valid Python file extension
|
||||
EXT_PYTHON = ".py"
|
||||
EXT_PYTHON_DISABLED = ".disabled"
|
||||
|
||||
# DIRSPEC_: where all the test config files are stored
|
||||
# we hope that current working directory is always in pelican/pelican/tests
|
||||
DIRSPEC_DATADIR: str = "settings" + os.sep
|
||||
DIRSPEC_RELATIVE: str = DIRSPEC_DATADIR # reuse 'tests/settings/' as scratch area
|
||||
|
||||
# PC_ = Pelican Configuration or PELICANCONF or pelicanconf
|
||||
# FILENAME_: file name without the extension
|
||||
PC_FILENAME_DEFAULT = "pelicanconf"
|
||||
PC_FILENAME_INDENT1_ERROR = "pelicanconf-indent1-error"
|
||||
PC_FILENAME_INDENT2_ERROR = "pelicanconf-indent2-error"
|
||||
PC_FILENAME_SYNTAX3_ERROR = "pelicanconf-syntax3-error"
|
||||
PC_FILENAME_SYNTAX4_ERROR = "pelicanconf-syntax4-error"
|
||||
|
||||
# MODNAME_ = Module name
|
||||
PC_MODNAME_DEFAULT = "pelicanconf" # used if module_name is blank
|
||||
PC_MODNAME_INDENT1_ERROR = "pelicanconf-indent1-error" # PyPA error: no ending digit
|
||||
PC_MODNAME_INDENT2_ERROR = "pelicanconf-indent2-error" # PyPA error: no ending digit
|
||||
PC_MODNAME_SYNTAX3_ERROR = "pelicanconf-syntax3-error" # PyPA error: no ending digit
|
||||
PC_MODNAME_SYNTAX4_ERROR = "pelicanconf-syntax4-error" # PyPA error: no ending digit
|
||||
PC_MODNAME_SYS_BUILTIN = "calendar"
|
||||
|
||||
# Iterators, for fixtures
|
||||
PC_MODULES_EXPECTED = {PC_MODNAME_SYS_BUILTIN}
|
||||
PC_MODULES_TEST = {
|
||||
PC_MODNAME_DEFAULT,
|
||||
PC_MODNAME_INDENT1_ERROR,
|
||||
PC_MODNAME_INDENT2_ERROR,
|
||||
PC_MODNAME_SYNTAX3_ERROR,
|
||||
PC_MODNAME_SYNTAX4_ERROR,
|
||||
}
|
||||
|
||||
TMP_FILENAME_SUFFIX = PC_FILENAME_DEFAULT
|
||||
|
||||
# FULLNAME_: filename + extension
|
||||
PC_FULLNAME_INDENT1_ERROR: str = PC_FILENAME_INDENT1_ERROR + EXT_PYTHON
|
||||
PC_FULLNAME_INDENT2_ERROR: str = PC_FILENAME_INDENT2_ERROR + EXT_PYTHON
|
||||
PC_FULLNAME_SYNTAX3_ERROR: str = PC_FILENAME_SYNTAX3_ERROR + EXT_PYTHON
|
||||
PC_FULLNAME_SYNTAX4_ERROR: str = PC_FILENAME_SYNTAX4_ERROR + EXT_PYTHON
|
||||
# BLOB_: a file trying to hide from ruff/black syntax checkers for our syntax tests
|
||||
BLOB_FULLNAME_INDENT1_ERROR = PC_FULLNAME_INDENT1_ERROR + EXT_PYTHON_DISABLED
|
||||
BLOB_FULLNAME_INDENT2_ERROR = PC_FULLNAME_INDENT2_ERROR + EXT_PYTHON_DISABLED
|
||||
BLOB_FULLNAME_SYNTAX3_ERROR = PC_FULLNAME_SYNTAX3_ERROR + EXT_PYTHON_DISABLED
|
||||
BLOB_FULLNAME_SYNTAX4_ERROR = PC_FULLNAME_SYNTAX4_ERROR + EXT_PYTHON_DISABLED
|
||||
|
||||
# DIRNAME_: a construct of where to find config file for specific test
|
||||
PC_DIRNAME_NOACCESS: str = "unreadable-directory"
|
||||
|
||||
# DIRSPEC_: the full directory path
|
||||
|
||||
# Our test files
|
||||
BLOB_FILESPEC_INDENT1_ERROR = Path(DIRSPEC_DATADIR) / str(
|
||||
PC_FULLNAME_INDENT1_ERROR + EXT_PYTHON_DISABLED
|
||||
)
|
||||
BLOB_FILESPEC_INDENT2_ERROR = Path(DIRSPEC_DATADIR) / str(
|
||||
PC_FULLNAME_INDENT2_ERROR + EXT_PYTHON_DISABLED
|
||||
)
|
||||
BLOB_FILESPEC_SYNTAX3_ERROR = Path(DIRSPEC_DATADIR) / str(
|
||||
PC_FULLNAME_SYNTAX3_ERROR + EXT_PYTHON_DISABLED
|
||||
)
|
||||
BLOB_FILESPEC_SYNTAX4_ERROR = Path(DIRSPEC_DATADIR) / str(
|
||||
PC_FULLNAME_SYNTAX4_ERROR + EXT_PYTHON_DISABLED
|
||||
)
|
||||
|
||||
# PATH_: the final path for unit tests here
|
||||
# FILESPEC_: the full path + filename + extension
|
||||
# REL_: relative path
|
||||
RO_FILESPEC_REL_INDENT1_ERROR_PATH = Path(DIRSPEC_DATADIR) / PC_FULLNAME_INDENT1_ERROR
|
||||
RO_FILESPEC_REL_INDENT2_ERROR_PATH = Path(DIRSPEC_DATADIR) / PC_FULLNAME_INDENT2_ERROR
|
||||
RO_FILESPEC_REL_SYNTAX3_ERROR_PATH = Path(DIRSPEC_DATADIR) / PC_FULLNAME_SYNTAX3_ERROR
|
||||
RO_FILESPEC_REL_SYNTAX4_ERROR_PATH = Path(DIRSPEC_DATADIR) / PC_FULLNAME_SYNTAX4_ERROR
|
||||
|
||||
# SyntaxError placement for use with settings/pelicanconf-syntax-error.py
|
||||
SM_UT_SYNTAX_INDENT1_LINENO = 5 # tests/settings/pelicanconf-indent-error1.py.disabled
|
||||
SM_UT_SYNTAX_INDENT1_OFFSET = 1 # tests/settings/pelicanconf-indent-error1.py.disabled
|
||||
SM_UT_SYNTAX_INDENT2_LINENO = 13 # tests/settings/pelicanconf-indent-error2.py.disabled
|
||||
SM_UT_SYNTAX_INDENT2_OFFSET = 5 # tests/settings/pelicanconf-indent-error2.py.disabled
|
||||
SM_UT_SYNTAX_ERROR3_LINENO = 13 # tests/settings/pelicanconf-syntax-error3.py.disabled
|
||||
SM_UT_SYNTAX_ERROR3_OFFSET = 5 # tests/settings/pelicanconf-syntax-error3.py.disabled
|
||||
SM_UT_SYNTAX_ERROR4_LINENO = 5 # tests/settings/pelicanconf-syntax-error4.py.disabled
|
||||
SM_UT_SYNTAX_ERROR4_OFFSET = 1 # tests/settings/pelicanconf-syntax-error4.py.disabled
|
||||
|
||||
load_source_argument_list_count = 2
|
||||
|
||||
# Code starts here
|
||||
# logging.basicConfig(level=0)
|
||||
# log = logging.getLogger(__name__)
|
||||
# logging.root.setLevel(logging.DEBUG)
|
||||
# log.propagate = True
|
||||
|
||||
args = inspect.getfullargspec(load_source)
|
||||
if ("name" not in args.args) and (args.args.__len__ != load_source_argument_list_count):
|
||||
# Skip this entire test file if load_source() only supports 1 argument
|
||||
pytest.mark.skip(
|
||||
"this class is only used with load_source() having "
|
||||
"support for a 'name' argument"
|
||||
)
|
||||
|
||||
# We need an existing Python system built-in module for testing load_source.
|
||||
if PC_MODNAME_SYS_BUILTIN not in sys.modules:
|
||||
pytest.exit(
|
||||
errno.EACCES,
|
||||
"PC_MODNAME_SYS_BUILTIN variable MUST BE an existing system "
|
||||
"builtin module; this test is aborted",
|
||||
)
|
||||
|
||||
# Oppositional, PC_MODNAME_DEFAULT must NOT be a pre-existing system built-in module
|
||||
if PC_MODNAME_DEFAULT in sys.modules:
|
||||
# We are not authorized to tamper outside our test area
|
||||
pytest.exit(
|
||||
errno.EACCES,
|
||||
f" Cannot reuses a system built-in module {PC_MODNAME_DEFAULT};"
|
||||
" this test is aborted",
|
||||
)
|
||||
|
||||
|
||||
##########################################################################
|
||||
# module-based fixtures
|
||||
# (session-based fixtures resides in `conftest.py` file.)
|
||||
##########################################################################
|
||||
@pytest.fixture(scope="module")
|
||||
def cwd_inside_tests_dir__fixture_module(get_tests_dir__fixture_session):
|
||||
"""Work inside the `tests/` directory
|
||||
|
||||
This pytest module-wide fixture will change the current working directory
|
||||
to the `pelican/pelican/tests` directory, run the unit test, then return
|
||||
back to its original directory.
|
||||
|
||||
This fixture gets evoked exactly once (file-wide) due to `scope=module`.
|
||||
|
||||
:return: Returns the path of the `tests/` directory
|
||||
:rtype: pathlib.Path"""
|
||||
tests_dir_path = get_tests_dir__fixture_session
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(tests_dir_path)
|
||||
|
||||
yield Path(tests_dir_path)
|
||||
|
||||
os.chdir(original_cwd)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def cwd_inside_pelican_source_dir__fixture_module(
|
||||
get_pelican_source_dir__fixture_session,
|
||||
):
|
||||
"""Work inside the Pelican source directory
|
||||
|
||||
This pytest module-wide fixture will change the current working directory
|
||||
to the Pelican source directory, run the unit test, then return back to its
|
||||
original directory.
|
||||
|
||||
This fixture gets evoked exactly once (file-wide) due to `scope=module`.
|
||||
|
||||
:return: Returns the Path of the Pelican source directory
|
||||
:rtype: pathlib.Path"""
|
||||
pelican_source_dir_path = get_pelican_source_dir__fixture_session
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(pelican_source_dir_path)
|
||||
|
||||
yield Path(pelican_source_dir_path)
|
||||
|
||||
os.chdir(original_cwd)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def cwd_inside_pelican_package_dir__fixture_module(
|
||||
get_pelican_package_dir__fixture_session,
|
||||
):
|
||||
"""Work inside the Pelican package root directory
|
||||
|
||||
This pytest module-wide fixture will change the current working directory
|
||||
to the Pelican root directory, run the unit test, then return back to its
|
||||
original directory.
|
||||
|
||||
This fixture gets evoked exactly once (file-wide) due to `scope=module`.
|
||||
|
||||
:return: Returns the Path of the Pelican root directory
|
||||
:rtype: pathlib.Path"""
|
||||
pelican_package_dir_path = get_pelican_package_dir__fixture_session
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(pelican_package_dir_path)
|
||||
|
||||
yield Path(pelican_package_dir_path)
|
||||
|
||||
os.chdir(original_cwd)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def cwd_inside_pelican_pkg_dir_rel_path_fixture_module(get_tests_dir__fixture_session):
|
||||
"""Change to Pelican package root directory
|
||||
|
||||
This pytest module-wide fixture will change the current working directory
|
||||
to the Pelican root directory, run the unit test, then return back to its
|
||||
original directory.
|
||||
|
||||
This fixture gets evoked exactly once (file-wide) due to `scope=module`.
|
||||
|
||||
:return: Returns the Path of the relative Pelican root directory (always ".")
|
||||
:rtype: pathlib.Path"""
|
||||
test_abs_dirpath: Path = Path(__file__).parent # secret sauce
|
||||
pelican_source_subdir_path = test_abs_dirpath.parent
|
||||
pelican_package_dir_path = pelican_source_subdir_path.parent
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(pelican_package_dir_path)
|
||||
|
||||
yield Path(".")
|
||||
|
||||
os.chdir(original_cwd)
|
||||
|
||||
|
||||
##########################################################################
|
||||
# module-specific (test_settings_syntax.py) functions
|
||||
##########################################################################
|
||||
def expected_in_sys_modules(module_name: str) -> bool:
|
||||
if module_name in sys.modules:
|
||||
return True
|
||||
raise AssertionError(f"Module {module_name} no longer is in sys.modules[].")
|
||||
|
||||
|
||||
def not_expected_in_sys_modules(module_name: str) -> bool:
|
||||
if module_name not in sys.modules:
|
||||
return True
|
||||
raise AssertionError(f"Module {module_name} unexpectedly now in sys.modules[].")
|
||||
|
||||
|
||||
def check_module_integrity():
|
||||
# Check if any modules were left behind by previous unit test(s).
|
||||
for not_expected_module in PC_MODULES_TEST:
|
||||
not_expected_in_sys_modules(not_expected_module)
|
||||
|
||||
# Now check that we did not lose any critical/built-in module.
|
||||
for expected_module in PC_MODULES_EXPECTED:
|
||||
expected_in_sys_modules(expected_module)
|
||||
|
||||
|
||||
##########################################################################
|
||||
# All about the handling of module name
|
||||
##########################################################################
|
||||
class TestSettingsSyntax:
|
||||
"""load_source() with focus on Syntax Handling"""
|
||||
|
||||
##########################################################################
|
||||
# Per-class fixtures with focus on module name
|
||||
##########################################################################
|
||||
|
||||
##########################################################################
|
||||
# Function-specific (per unit test) fixtures with focus on module name
|
||||
##########################################################################
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
def inject_fixtures(self, caplog):
|
||||
"""Save the console output by logger"""
|
||||
self._caplog = caplog
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def assert_module_integrity__fixture_func(
|
||||
self,
|
||||
serialize_sys_modules__fixture_session,
|
||||
assert_module_integrity__fixture_session,
|
||||
):
|
||||
"""Check for integrity of sys.modules[] thoroughly"""
|
||||
check_module_integrity()
|
||||
yield
|
||||
check_module_integrity()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def cwd_inside_tempdir__fixture_func(
|
||||
self, temp_dir_all_unit_tests__fixture_session
|
||||
):
|
||||
"""Work inside the temporary directory"""
|
||||
tmp_dir = temp_dir_all_unit_tests__fixture_session
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(tmp_dir)
|
||||
|
||||
yield tmp_dir
|
||||
|
||||
os.chdir(original_cwd)
|
||||
|
||||
##########################################################################
|
||||
# Test cases with focus on IndentationError syntax handling of Python file
|
||||
##########################################################################
|
||||
def test_load_source_module_str_abs_indent1_error_fail(
|
||||
self,
|
||||
locale_to_c__fixture_session,
|
||||
assert_module_integrity__fixture_func,
|
||||
get_tests_settings_dir__fixture_session,
|
||||
cwd_inside_tempdir__fixture_func,
|
||||
):
|
||||
""" "syntax error; absolute path, str type; passing mode"""
|
||||
# In Pelican, module name shall always be 'pelicanconf
|
||||
default_module = PC_MODNAME_INDENT1_ERROR
|
||||
not_expected_in_sys_modules(default_module)
|
||||
# identify blob of "pseudo-script" file (ruff/black avoidance of syntax-error)
|
||||
blob: str = str(
|
||||
get_tests_settings_dir__fixture_session / BLOB_FULLNAME_INDENT1_ERROR
|
||||
)
|
||||
# Set up temporary absolute "/$TEMPDIR/pelicanXXXXXX/(here)"
|
||||
tmp_dir_abs_path: Path = cwd_inside_tempdir__fixture_func
|
||||
indent_error_file_abs_str: str = str(
|
||||
tmp_dir_abs_path / PC_FULLNAME_INDENT1_ERROR
|
||||
)
|
||||
# despite tempdir, check if file does NOT exist
|
||||
if Path(indent_error_file_abs_str).exists():
|
||||
# Bad test setup, assert out
|
||||
raise AssertionError(
|
||||
f"File {indent_error_file_abs_str} should not " "exist in tempdir"
|
||||
)
|
||||
# Copy mangled pseudo-Python file into temporary absolute area as a Python file
|
||||
shutil.copyfile(blob, indent_error_file_abs_str)
|
||||
|
||||
with self._caplog.at_level(logging.DEBUG):
|
||||
self._caplog.clear()
|
||||
with pytest.raises(SyntaxError) as sample:
|
||||
# ignore return value due to sys.exit()
|
||||
load_source(default_module, indent_error_file_abs_str)
|
||||
assert sample.type == SyntaxError
|
||||
# TODO Issue #09005 - say something to the end-user exactly where syntax err is
|
||||
# assert "unexpected indent" in self._caplog.text
|
||||
|
||||
# Cleanup
|
||||
if expected_in_sys_modules(default_module):
|
||||
del sys.modules[default_module]
|
||||
Path(indent_error_file_abs_str).unlink(missing_ok=False)
|
||||
|
||||
def test_load_source_module_str_rel_indent2_error_fail(
|
||||
self,
|
||||
locale_to_c__fixture_session,
|
||||
assert_module_integrity__fixture_func,
|
||||
get_tests_settings_dir__fixture_session,
|
||||
cwd_inside_tempdir__fixture_func,
|
||||
):
|
||||
"""syntax error, relative path, str type; failing mode"""
|
||||
# In Pelican, module name shall always be 'pelicanconf'
|
||||
indent_error_module = PC_MODNAME_INDENT2_ERROR
|
||||
not_expected_in_sys_modules(indent_error_module)
|
||||
# copy "pseudo-script" file into 'settings/pelicanXXXXX/(here)'
|
||||
# An essential avoidance of ruff/black's own syntax-error asserts
|
||||
blob: str = str(
|
||||
get_tests_settings_dir__fixture_session / BLOB_FULLNAME_INDENT2_ERROR
|
||||
)
|
||||
# Set up temporary relative "settings/pelicanXXXXXX/(here)"
|
||||
tmp_rel_dirspec_path: Path = cwd_inside_tempdir__fixture_func
|
||||
indent_error_file_str: str = str(
|
||||
tmp_rel_dirspec_path / PC_FULLNAME_INDENT2_ERROR
|
||||
)
|
||||
# Copy mangled pseudo-Python file into temporary area as a Python file
|
||||
shutil.copyfile(blob, indent_error_file_str)
|
||||
|
||||
with self._caplog.at_level(logging.DEBUG):
|
||||
self._caplog.clear()
|
||||
with pytest.raises(SyntaxError) as sample:
|
||||
# ignore return value due to sys.exit()
|
||||
load_source(indent_error_module, path=indent_error_file_str)
|
||||
|
||||
assert sample.type == SyntaxError
|
||||
assert sample.value.lineno == SM_UT_SYNTAX_INDENT2_LINENO
|
||||
assert sample.value.offset == SM_UT_SYNTAX_INDENT2_OFFSET
|
||||
# Would be nice if a STDERR says something to end-user WHERE
|
||||
# the syntax err is # TODO Issue #09005
|
||||
# assert "unexpected indent" in self._caplog.text
|
||||
|
||||
if expected_in_sys_modules(indent_error_module):
|
||||
del sys.modules[indent_error_module]
|
||||
not_expected_in_sys_modules(indent_error_module)
|
||||
Path(indent_error_file_str).unlink(missing_ok=False)
|
||||
|
||||
def test_load_source_module_path_rel_syntax3_error_fail(
|
||||
self,
|
||||
locale_to_c__fixture_session,
|
||||
assert_module_integrity__fixture_func,
|
||||
get_tests_settings_dir__fixture_session,
|
||||
cwd_inside_tempdir__fixture_func,
|
||||
):
|
||||
"""Syntax error; valid relative file, Path type; valid module; passing mode"""
|
||||
# In Pelican, module name shall always be 'pelicanconf'
|
||||
default_module = PC_MODNAME_SYNTAX3_ERROR
|
||||
not_expected_in_sys_modules(default_module)
|
||||
# identify blob of "pseudo-script" file (ruff/black avoidance of syntax-error)
|
||||
blob: str = str(
|
||||
get_tests_settings_dir__fixture_session / BLOB_FULLNAME_SYNTAX3_ERROR
|
||||
)
|
||||
# Set up temporary relative "settings/pelicanXXXXXX/(here)"
|
||||
tmp_rel_dirspec_path: Path = cwd_inside_tempdir__fixture_func
|
||||
syntax_err_rel_filespec_path: Path = (
|
||||
tmp_rel_dirspec_path / PC_FULLNAME_SYNTAX3_ERROR
|
||||
)
|
||||
# despite tempdir, check if file does NOT exist
|
||||
if syntax_err_rel_filespec_path.exists():
|
||||
# Bad test setup, assert out
|
||||
raise AssertionError(
|
||||
f"File {syntax_err_rel_filespec_path!s} should not exist in tempdir"
|
||||
)
|
||||
# Copy mangled pseudo-Python file into temporary absolute area as a Python file
|
||||
shutil.copyfile(blob, syntax_err_rel_filespec_path)
|
||||
|
||||
with self._caplog.at_level(logging.DEBUG):
|
||||
self._caplog.clear()
|
||||
with pytest.raises(SyntaxError) as sample:
|
||||
# ignore return value due to sys.exit()
|
||||
load_source(default_module, path=syntax_err_rel_filespec_path)
|
||||
assert sample.type == SyntaxError
|
||||
assert sample.value.lineno == SM_UT_SYNTAX_ERROR3_LINENO
|
||||
assert sample.value.offset == SM_UT_SYNTAX_ERROR3_OFFSET
|
||||
# TODO Issue #09005 - say something to the end-user exactly where syntax err is
|
||||
# assert "unexpected indent" in self._caplog.text
|
||||
|
||||
if expected_in_sys_modules(default_module):
|
||||
del sys.modules[default_module]
|
||||
Path(syntax_err_rel_filespec_path).unlink(missing_ok=True)
|
||||
|
||||
def test_load_source_module_path_abs_syntax4_error_fail(
|
||||
self,
|
||||
locale_to_c__fixture_session, # internationalization
|
||||
assert_module_integrity__fixture_func, # integrity of sys.modules via serialization
|
||||
get_tests_settings_dir__fixture_session, # tests/settings
|
||||
cwd_inside_tempdir__fixture_func, # change working dir to a temporary directory
|
||||
):
|
||||
"""Syntax error; valid absolute file, Path type; valid module; passing mode"""
|
||||
# In Pelican, module name shall always be 'pelicanconf'
|
||||
default_module = PC_MODNAME_SYNTAX4_ERROR
|
||||
not_expected_in_sys_modules(default_module)
|
||||
# Set up temporary absolute "/$TEMPDIR/pelicanXXXXXX/(here)"
|
||||
tmp_abs_dirspec_path: Path = cwd_inside_tempdir__fixture_func
|
||||
syntax_err_abs_filespec_path: Path = (
|
||||
tmp_abs_dirspec_path / PC_FULLNAME_SYNTAX4_ERROR
|
||||
)
|
||||
# copy "pseudo-script" file to '/tmp' (ruff/black avoidance of syntax-error)
|
||||
blob: Path = (
|
||||
get_tests_settings_dir__fixture_session / BLOB_FULLNAME_SYNTAX4_ERROR
|
||||
)
|
||||
# despite tempdir, check if file does NOT exist
|
||||
if Path(syntax_err_abs_filespec_path).exists():
|
||||
# Bad test setup, assert out
|
||||
raise AssertionError(
|
||||
f"File {syntax_err_abs_filespec_path} should not " "exist in tempdir"
|
||||
)
|
||||
# Copy mangled pseudo-Python file into temporary area as a Python file
|
||||
shutil.copyfile(blob, syntax_err_abs_filespec_path)
|
||||
|
||||
with self._caplog.at_level(logging.DEBUG):
|
||||
self._caplog.clear()
|
||||
with pytest.raises(IndentationError) as sample:
|
||||
load_source(default_module, syntax_err_abs_filespec_path)
|
||||
|
||||
assert sample.type == IndentationError
|
||||
assert sample.value.lineno == SM_UT_SYNTAX_ERROR4_LINENO
|
||||
assert sample.value.offset == SM_UT_SYNTAX_ERROR4_OFFSET
|
||||
# TODO Issue #09005 - say something to the end-user exactly where syntax err is
|
||||
# assert "unexpected indent" in self._caplog.text
|
||||
|
||||
# Cleanup temporary
|
||||
if expected_in_sys_modules(default_module):
|
||||
del sys.modules[default_module]
|
||||
Path(syntax_err_abs_filespec_path).unlink(missing_ok=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# if executing this file alone, it tests this file alone.
|
||||
# Can execute from any current working directory
|
||||
pytest.main([__file__])
|
||||
|
||||
# more, complex variants of pytest.
|
||||
# pytest.main([__file__, "-n0", "-rAw", "--capture=no", "--no-header"])
|
||||
# pytest.main([__file__, "-n0"]) # single-process, single-thread
|
||||
Loading…
Add table
Add a link
Reference in a new issue