Source code for pytest_houdini.fixtures.nodes

"""Fixtures related to nodes."""

# Future
from __future__ import annotations

# Standard Library
import contextlib
from typing import TYPE_CHECKING

# Third Party
import pytest

# pytest-houdini
from pytest_houdini.fixtures.exceptions import (
    NoTestNodeError,
    TestNodeDoesNotContainSOPsError,
)

# Houdini
import hou

if TYPE_CHECKING:
    from collections.abc import Callable, Generator


# Non-Public Functions


def _find_matching_node(parent: hou.OpNode, request: pytest.FixtureRequest) -> hou.OpNode:
    """Try to find a matching child node based on a test request.

    Node search order is as follows:
    - Node matching the exact test name
    - Node matching the class name + test name (minus 'test_' prefix for function name)
    - Node matching the class name / test name (minus 'test_' prefix for function name)
    - Node matching the class name

    The class name can be either the raw name or entirely lowercase.

    Args:
        parent: The parent node to search under.
        request: A fixture request with which to find a node.

    Returns:
        A child node matching the request, if any.

    Raises:
         NoTestNodeError: Will be raised if no matching node could be found.
    """
    test_name = request.node.originalname

    # First try to find a node with the exact test name.
    names = [test_name]

    if request.cls is not None:
        cls_name = request.cls.__name__

        test_name = test_name[5:]

        names.extend([
            # Look for a node with the class name + test name (minus test_ from function name)
            f"{cls_name}_{test_name}",
            f"{cls_name.lower()}_{test_name}",
            # Also support the test node being under a parent node based on the class name.
            f"{cls_name}/{test_name}",
            f"{cls_name.lower()}/{test_name}",
            # Finally try to find a node with the class name.
            cls_name,
            cls_name.lower(),
        ])

    for name in names:
        node = parent.node(name)

        if node is not None:
            return node

    searched_paths = [parent.path() + "/" + name for name in names]

    raise NoTestNodeError(searched_paths)


# Fixtures


[docs] @pytest.fixture def create_temp_node() -> Generator[Callable]: """Fixture to create a temporary node that will be destroyed on cleanup.""" created_nodes_: list[hou.Node] = [] def _create( parent: hou.Node, node_type_name: str, node_name: str | None = None, *, run_init_scripts: bool = True ) -> hou.Node: """Function to create a test node that will be destroyed on cleanup. Args: parent: The parent to create the test node under. node_type_name: The node type to create. node_name: Optional node name. run_init_scripts: Whether to run the node initialization scripts. Return: The created test node. """ node = parent.createNode(node_type_name, node_name, run_init_scripts=run_init_scripts) created_nodes_.append(node) return node yield _create for created in created_nodes_: with contextlib.suppress(hou.ObjectWasDeleted): created.destroy()
[docs] @pytest.fixture def obj_test_node(request: pytest.FixtureRequest) -> hou.OpNode: """Fixture to provide a node in /obj matching the test.""" parent = hou.node("/obj") return _find_matching_node(parent, request)
[docs] @pytest.fixture def obj_test_geo(obj_test_node: hou.OpNode) -> hou.Geometry: """Fixture to provide the read-only display node geometry of a node in /obj matching the test.""" if obj_test_node.childTypeCategory() != hou.sopNodeTypeCategory(): raise TestNodeDoesNotContainSOPsError(obj_test_node) return obj_test_node.displayNode().geometry()
[docs] @pytest.fixture def obj_test_geo_copy(obj_test_geo: hou.Geometry) -> hou.Geometry: """Fixture to get a writable copy of the display node geometry of a node in /obj matching the test.""" geo = hou.Geometry() geo.merge(obj_test_geo) return geo
[docs] @pytest.fixture def out_test_node(request: pytest.FixtureRequest) -> hou.OpNode: """Fixture to provide a node in /out matching the test.""" parent = hou.node("/out") return _find_matching_node(parent, request)