forked from Freedom-of-Form-Foundation/anatomy3d-blender
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CI] Add workflow for pytest-blender unit tests (#3)
* Add some very simple unit tests * [CI] Add workflow for pytest-blender * Add some unit tests for math.py
- Loading branch information
Showing
4 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
name: pytest-blender | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- geoscript-pytest-configuration | ||
pull_request: | ||
branches: | ||
- main | ||
- geoscript-pytest-mypy-ci | ||
|
||
jobs: | ||
test: | ||
name: Test with Blender ${{ matrix.blender-version }} on ${{ matrix.platform }} | ||
runs-on: ${{ matrix.platform }} | ||
strategy: | ||
matrix: | ||
platform: | ||
- ubuntu-latest | ||
- macOS-latest | ||
blender-version: | ||
- '3.3.0' | ||
- '3.3.1' | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python v3.10 | ||
uses: actions/setup-python@v3 | ||
with: | ||
python-version: "3.10" | ||
- name: Upgrade PIP | ||
run: python -m pip install --upgrade pip | ||
- name: Cache Blender ${{ matrix.blender-version }} | ||
uses: actions/cache@v3 | ||
id: cache-blender | ||
with: | ||
path: | | ||
~/blender-* | ||
~/_blender-executable-path.txt | ||
key: ${{ runner.os }}-${{ matrix.blender-version }} | ||
- name: Download Blender ${{ matrix.blender-version }} | ||
if: steps.cache-blender.outputs.cache-hit != 'true' | ||
id: download-blender | ||
run: | | ||
python -m pip install --upgrade blender-downloader | ||
printf "%s" "$(blender-downloader \ | ||
${{ matrix.blender-version }} --extract --remove-compressed \ | ||
--output-directory ~/ \ | ||
--quiet --print-blender-executable)" > ~/_blender-executable-path.txt | ||
- name: Install dependencies | ||
id: install-dependencies | ||
run: | | ||
python -m pip install pytest-blender pytest | ||
blender_executable="$(< ~/_blender-executable-path.txt)" | ||
python_blender_executable="$(pytest-blender --blender-executable $blender_executable)" | ||
$python_blender_executable -m ensurepip | ||
$python_blender_executable -m pip install pytest | ||
echo "blender-executable=$blender_executable" >> $GITHUB_OUTPUT | ||
- name: Test with pytest | ||
run: pytest -svv --blender-executable "${{ steps.install-dependencies.outputs.blender-executable }}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/usr/bin/python3 | ||
|
||
"""This package contains unit tests for Geoscript""" | ||
|
||
# This file intentionally contains no code. See the other .py files in this package | ||
# for the specific unit tests. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
#!/usr/bin/python3 | ||
|
||
import pytest | ||
import bpy | ||
from .. import math as g | ||
from ..nodetrees import GeometryNodeTree | ||
|
||
|
||
test_unary_operations = [ | ||
(g.sqrt, "SQRT"), | ||
(g.inverse_sqrt, "INVERSE_SQRT"), | ||
(g.exp, "EXPONENT"), | ||
(g.sign, "SIGN"), | ||
(g.fract, "FRACT"), | ||
(g.sin, "SINE"), | ||
(g.cos, "COSINE"), | ||
(g.tan, "TANGENT"), | ||
(g.asin, "ARCSINE"), | ||
(g.acos, "ARCCOSINE"), | ||
(g.atan, "ARCTANGENT"), | ||
(g.sinh, "SINH"), | ||
(g.cosh, "COSH"), | ||
(g.tanh, "TANH"), | ||
] | ||
|
||
|
||
test_binary_operations = [ | ||
(g.log, "LOGARITHM"), | ||
(g.power, "POWER"), | ||
(g.minimum, "MINIMUM"), | ||
(g.maximum, "MAXIMUM"), | ||
(g.snap, "SNAP"), | ||
(g.pingpong, "PINGPONG"), | ||
(g.atan2, "ARCTAN2"), | ||
] | ||
|
||
|
||
test_swapped_operations = [ | ||
(g.step, "LESS_THAN"), # Swapped meaning: greater than or equal to. | ||
(g.drop, "GREATER_THAN"), # Swapped meaning: smaller than or equal to. | ||
] | ||
|
||
|
||
test_tertiary_operations = [ | ||
(g.multiply_add, "MULTIPLY_ADD"), | ||
(g.compare, "COMPARE"), | ||
(g.smooth_min, "SMOOTH_MIN"), | ||
(g.smooth_max, "SMOOTH_MAX"), | ||
(g.wrap, "WRAP"), | ||
] | ||
|
||
|
||
@pytest.mark.parametrize("test_function,test_operation_name", test_tertiary_operations) | ||
def test_tertiary(test_function, test_operation_name: str): | ||
tree = GeometryNodeTree("test_add_input") | ||
input1 = tree.InputFloat() | ||
input2 = tree.InputFloat() | ||
input3 = tree.InputFloat() | ||
output = test_function(input1, input2, input3) | ||
# Check if the sockets are linked: | ||
bl_node = output.socket_reference.node | ||
assert isinstance(bl_node, bpy.types.ShaderNodeMath) | ||
assert bl_node.operation == test_operation_name | ||
assert bl_node.inputs[0].is_linked | ||
assert bl_node.inputs[1].is_linked | ||
assert bl_node.inputs[2].is_linked | ||
# Check if the sockets link correctly: | ||
group_inputs = tree.node_tree.nodes["Group Input"].outputs | ||
assert isinstance(bl_node.inputs[0].links, tuple) | ||
assert bl_node.inputs[0].links[0].from_socket == group_inputs[0] | ||
assert isinstance(bl_node.inputs[1].links, tuple) | ||
assert bl_node.inputs[1].links[0].from_socket == group_inputs[1] | ||
assert isinstance(bl_node.inputs[2].links, tuple) | ||
assert bl_node.inputs[2].links[0].from_socket == group_inputs[2] | ||
|
||
|
||
@pytest.mark.parametrize("test_function,test_operation_name", test_binary_operations) | ||
def test_binary(test_function, test_operation_name: str): | ||
tree = GeometryNodeTree("test_add_input") | ||
input1 = tree.InputFloat() | ||
input2 = tree.InputFloat() | ||
output = test_function(input1, input2) | ||
# Check if the sockets are linked: | ||
bl_node = output.socket_reference.node | ||
assert isinstance(bl_node, bpy.types.ShaderNodeMath) | ||
assert bl_node.operation == test_operation_name | ||
assert bl_node.inputs[0].is_linked | ||
assert bl_node.inputs[1].is_linked | ||
assert not bl_node.inputs[2].is_linked | ||
# Check if the sockets link correctly: | ||
group_inputs = tree.node_tree.nodes["Group Input"].outputs | ||
assert isinstance(bl_node.inputs[0].links, tuple) | ||
assert bl_node.inputs[0].links[0].from_socket == group_inputs[0] | ||
assert isinstance(bl_node.inputs[1].links, tuple) | ||
assert bl_node.inputs[1].links[0].from_socket == group_inputs[1] | ||
|
||
|
||
@pytest.mark.parametrize("test_function,test_operation_name", test_swapped_operations) | ||
def test_swapped_binary(test_function, test_operation_name: str): | ||
tree = GeometryNodeTree("test_add_input") | ||
input1 = tree.InputFloat() | ||
input2 = tree.InputFloat() | ||
output = test_function(input1, input2) | ||
# Check if the sockets are linked: | ||
bl_node = output.socket_reference.node | ||
assert isinstance(bl_node, bpy.types.ShaderNodeMath) | ||
assert bl_node.operation == test_operation_name | ||
assert bl_node.inputs[0].is_linked | ||
assert bl_node.inputs[1].is_linked | ||
assert not bl_node.inputs[2].is_linked | ||
# Check if the sockets link correctly: | ||
group_inputs = tree.node_tree.nodes["Group Input"].outputs | ||
assert isinstance(bl_node.inputs[0].links, tuple) | ||
assert bl_node.inputs[0].links[0].from_socket == group_inputs[1] | ||
assert isinstance(bl_node.inputs[1].links, tuple) | ||
assert bl_node.inputs[1].links[0].from_socket == group_inputs[0] | ||
|
||
|
||
@pytest.mark.parametrize("test_function,test_operation_name", test_unary_operations) | ||
def test_unary(test_function, test_operation_name: str): | ||
tree = GeometryNodeTree("test_add_input") | ||
input1 = tree.InputFloat() | ||
output = test_function(input1) | ||
# Check if the sockets are linked: | ||
bl_node = output.socket_reference.node | ||
assert isinstance(bl_node, bpy.types.ShaderNodeMath) | ||
assert bl_node.operation == test_operation_name | ||
assert bl_node.inputs[0].is_linked | ||
assert not bl_node.inputs[1].is_linked | ||
assert not bl_node.inputs[2].is_linked | ||
# Check if the sockets link correctly: | ||
group_inputs = tree.node_tree.nodes["Group Input"].outputs | ||
assert isinstance(bl_node.inputs[0].links, tuple) | ||
assert bl_node.inputs[0].links[0].from_socket == group_inputs[0] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
#!/usr/bin/python3 | ||
|
||
import pytest | ||
import bpy | ||
from ..nodetrees import GeometryNodeTree | ||
|
||
|
||
def test_geometry_node_tree_class() -> None: | ||
"""Tests whether a simple GeometryNodeTree class can correctly add an input.""" | ||
class ExampleTree(GeometryNodeTree): | ||
def function(self): | ||
# Add new nodes to the tree: | ||
input1 = self.InputGeometry() | ||
|
||
example_tree = ExampleTree("test_function") | ||
bl_tree = example_tree.get_bl_tree() | ||
assert isinstance(bl_tree, bpy.types.GeometryNodeTree) | ||
inputs = bl_tree.inputs | ||
assert len(inputs) == 1 | ||
first_input = inputs[0] | ||
assert isinstance(first_input, bpy.types.NodeSocketInterfaceStandard) | ||
assert first_input.type == "GEOMETRY" | ||
assert isinstance(first_input, bpy.types.NodeSocketInterfaceGeometry) | ||
|
||
|
||
def add_input(example_tree: GeometryNodeTree, bpy_type: str) -> None: | ||
if bpy_type == "VALUE": | ||
example_tree.InputFloat() | ||
elif bpy_type == "BOOLEAN": | ||
example_tree.InputBoolean() | ||
elif bpy_type == "VECTOR": | ||
example_tree.InputVector() | ||
elif bpy_type == "OBJECT": | ||
example_tree.InputObject() | ||
elif bpy_type == "GEOMETRY": | ||
example_tree.InputGeometry() | ||
|
||
|
||
test_input_types = [ | ||
("VALUE", bpy.types.NodeSocketInterfaceFloat), | ||
("BOOLEAN", bpy.types.NodeSocketInterfaceBool), | ||
("VECTOR", bpy.types.NodeSocketInterfaceVector), | ||
("OBJECT", bpy.types.NodeSocketInterfaceObject), | ||
("GEOMETRY", bpy.types.NodeSocketInterfaceGeometry), | ||
] | ||
|
||
|
||
@pytest.mark.parametrize("test_bpy_typename,test_bpy_type", test_input_types) | ||
def test_add_input(test_bpy_typename: str, test_bpy_type: type) -> None: | ||
"""Tests whether the input generation functions work correctly.""" | ||
example_tree = GeometryNodeTree("test_add_input") | ||
add_input(example_tree, test_bpy_typename) | ||
bl_tree = example_tree.get_bl_tree() | ||
assert isinstance(bl_tree, bpy.types.GeometryNodeTree) | ||
inputs = bl_tree.inputs | ||
assert len(inputs) == 1 | ||
first_input = inputs[0] | ||
assert isinstance(first_input, bpy.types.NodeSocketInterfaceStandard) | ||
assert first_input.type == test_bpy_typename | ||
assert isinstance(first_input, test_bpy_type) |