Source code for gemseo.utils.testing.pytest_conftest

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""Pytest helpers."""

from __future__ import annotations

import contextlib
import faulthandler
import os
import sys
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any

import matplotlib.testing.decorators
import pytest

from gemseo.core.base_factory import BaseFactory

if TYPE_CHECKING:
    from collections.abc import Generator


def __tmp_wd(tmp_path):
    """Generator to move into a temporary directory forth and back.

    Return the path to the temporary directory.
    """
    prev_cwd = Path.cwd()
    os.chdir(str(tmp_path))
    try:
        yield tmp_path
    finally:
        os.chdir(str(prev_cwd))


@pytest.fixture(scope="module")
def module_tmp_wd(tmp_path_factory):
    """Generator to move into a temporary subdirectory forth and back.

    Return the path to the temporary directory.
    """
    prev_cwd = Path.cwd()
    tmp_path = tmp_path_factory.getbasetemp()
    os.chdir(str(tmp_path))
    try:
        yield tmp_path
    finally:
        os.chdir(str(prev_cwd))


# Fixture to move into a temporary directory.
tmp_wd = pytest.fixture()(__tmp_wd)


[docs] def pytest_sessionstart(session) -> None: """Bypass console pollution from fortran code.""" # Prevent fortran code (like lbfgs from scipy) from writing to the console # by setting its stdout and stderr units to the standard file descriptors. # As a side effect, the files fort.{0,6} are created with the content of # the console outputs. os.environ["GFORTRAN_STDOUT_UNIT"] = "1" os.environ["GFORTRAN_STDERR_UNIT"] = "2"
[docs] def pytest_sessionfinish(session) -> None: """Remove file pollution from fortran code.""" # take care of pytest_sessionstart side effects for file_ in Path().glob("fort.*"): with contextlib.suppress(PermissionError): # On windows the file may be opened and not released by another component. file_.unlink()
@pytest.fixture(autouse=True) def skip_under_windows(request) -> None: """Fixture that add a marker to skip under windows. Use it like a usual skip marker. """ if request.node.get_closest_marker( "skip_under_windows" ) and sys.platform.startswith("win"): pytest.skip("skipped on windows") @pytest.fixture() def baseline_images(request): """Return the baseline_images contents. Used when the compare_images decorator has indirect set. """ return request.param @pytest.fixture() def reset_factory(): """Reset the factory cache.""" BaseFactory.clear_cache() yield BaseFactory.clear_cache() # Backup before we monkey patch. original_image_directories = matplotlib.testing.decorators._image_directories if "GEMSEO_KEEP_IMAGE_COMPARISONS" not in os.environ: # Context manager to change the current working directory to a temporary one. __ctx_tmp_wd = contextlib.contextmanager(__tmp_wd) def _image_directories(func): """Create the result_images directory in a temporary parent directory.""" with __ctx_tmp_wd(tempfile.mkdtemp()): baseline_dir, result_dir = original_image_directories(func) return baseline_dir, result_dir matplotlib.testing.decorators._image_directories = _image_directories # Fixtures to deal with the Excel disciplines. # Check the presence of xlwings, and skip accordingly. @pytest.fixture(scope="module") def disable_fault_handler() -> Generator[None, None, None]: """Generator to temporarily disable the fault handler. Return a call to disable the fault handler. """ if faulthandler.is_enabled(): try: faulthandler.disable() yield finally: faulthandler.enable() @pytest.fixture(scope="module") def import_or_skip_xlwings() -> Any: """Fixture to skip a test when xlwings cannot be imported.""" return pytest.importorskip("xlwings", reason="xlwings is not available") @pytest.fixture(scope="module") def is_xlwings_usable(import_or_skip_xlwings, disable_fault_handler) -> bool: """Check if xlwings is usable. Args: import_or_skip_xlwings: Fixture to import xlwings when available, otherwise skip the test. disable_fault_handler: Fixture to temporarily disable the fault handler. """ xlwings = import_or_skip_xlwings try: # Launch xlwings from a context manager to ensure it closes immediately. # See https://docs.xlwings.org/en/stable/whatsnew.html#v0-24-3-jul-15-2021 with xlwings.App(visible=False) as app: # noqa: F841 pass except: # noqa: E722,B001 return False else: return True @pytest.fixture(scope="module") def skip_if_xlwings_is_not_usable(is_xlwings_usable: bool) -> None: """Fixture to skip a test when xlwings is not usable.""" if not is_xlwings_usable: pytest.skip("This test requires excel.") @pytest.fixture(scope="module") def skip_if_xlwings_is_usable(is_xlwings_usable: bool) -> None: """Fixture to skip a test when xlwings is usable.""" if is_xlwings_usable: pytest.skip("This test is only required when excel is not available.")