"""
Unit tests for the LegacyGLMesh class in the PicoGL OpenGL backend.
This module contains a comprehensive suite of unit tests for verifying the correctness,
robustness, and interface of the :class:`picogl.renderer.legacy_glmesh.LegacyGLMesh`
class, which manages GPU-resident mesh data for legacy OpenGL compatibility.
The tests cover:
- Object initialization with various mesh data
- from_mesh_data class method
- GPU buffer upload and allocation with legacy VBOs
- Dynamic attribute generation for layout descriptors
- OpenGL binding/unbinding operations with legacy client states
- Drawing operations with legacy OpenGL functions
- Context manager functionality
- Resource cleanup and memory management
- Error handling and edge cases
- Data validation and type conversion
Dependencies:
- unittest (standard library)
- unittest.mock.MagicMock for OpenGL function mocking
- numpy for test data
- picogl.renderer.legacy_glmesh.LegacyGLMesh
- picogl.renderer.meshdata.MeshData
To run the tests::
python -m unittest picogl.tests.test_legacy_glmesh
"""
import unittest
from unittest.mock import MagicMock, patch, call
import numpy as np
from OpenGL.raw.GL._types import GL_FLOAT
from OpenGL.raw.GL.VERSION.GL_1_0 import GL_TRIANGLES, GL_UNSIGNED_INT
from OpenGL.raw.GL.VERSION.GL_1_1 import GL_COLOR_ARRAY, GL_VERTEX_ARRAY
from picogl.renderer.legacy_glmesh import LegacyGLMesh
from picogl.renderer.meshdata import MeshData
[docs]
class TestLegacyGLMesh(unittest.TestCase):
"""Test cases for LegacyGLMesh class."""
[docs]
def setUp(self):
"""Set up test fixtures before each test method."""
# Create test data
self.test_vertices = np.array([
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0]
], dtype=np.float32)
self.test_faces = np.array([0, 1, 2, 1, 3, 2], dtype=np.uint32)
self.test_colors = np.array([
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0]
], dtype=np.float32)
self.test_normals = np.array([
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0]
], dtype=np.float32)
self.test_uvs = np.array([
[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[1.0, 1.0]
], dtype=np.float32)
# Mock OpenGL functions to avoid context issues
self.gl_patches = [
patch('picogl.renderer.legacy_glmesh.glDrawElements'),
patch('picogl.renderer.legacy_glmesh.delete_buffer_object'),
patch('picogl.renderer.legacy_glmesh.legacy_client_states'),
patch('picogl.renderer.legacy_glmesh.create_layout'),
patch('picogl.renderer.legacy_glmesh.VertexBufferGroup'),
]
# Start all patches
for patch_obj in self.gl_patches:
patch_obj.start()
[docs]
def tearDown(self):
"""Clean up after each test method."""
# Stop all patches
for patch_obj in self.gl_patches:
patch_obj.stop()
[docs]
def test_initialization_with_all_parameters(self):
"""Test LegacyGLMesh initialization with all parameters."""
mesh = LegacyGLMesh(
vertices=self.test_vertices,
faces=self.test_faces,
colors=self.test_colors,
normals=self.test_normals,
uvs=self.test_uvs
)
# Test data assignments
np.testing.assert_array_equal(mesh.vertices, self.test_vertices)
np.testing.assert_array_equal(mesh.indices, self.test_faces)
np.testing.assert_array_equal(mesh.colors, self.test_colors)
np.testing.assert_array_equal(mesh.normals, self.test_normals)
np.testing.assert_array_equal(mesh.uvs, self.test_uvs)
# Test initial state
self.assertIsNone(mesh.vao)
self.assertEqual(mesh.index_count, 0)
[docs]
def test_initialization_with_minimal_parameters(self):
"""Test LegacyGLMesh initialization with only vertices and faces."""
mesh = LegacyGLMesh(
vertices=self.test_vertices,
faces=self.test_faces
)
# Test data assignments
np.testing.assert_array_equal(mesh.vertices, self.test_vertices)
np.testing.assert_array_equal(mesh.indices, self.test_faces)
# Test default values
expected_colors = np.tile((0.0, 0.0, 1.0), (4, 1)).astype(np.float32)
np.testing.assert_array_equal(mesh.colors, expected_colors)
expected_normals = np.zeros_like(self.test_vertices)
np.testing.assert_array_equal(mesh.normals, expected_normals)
expected_uvs = np.zeros((4, 2), dtype=np.float32)
np.testing.assert_array_equal(mesh.uvs, expected_uvs)
# Test initial state
self.assertIsNone(mesh.vao)
self.assertEqual(mesh.index_count, 0)
[docs]
def test_initialization_with_empty_faces(self):
"""Test LegacyGLMesh initialization with empty faces raises error."""
with self.assertRaises(ValueError) as context:
LegacyGLMesh(
vertices=self.test_vertices,
faces=np.array([], dtype=np.uint32)
)
self.assertIn("GLMesh requires non-empty faces", str(context.exception))
[docs]
def test_initialization_data_type_conversion(self):
"""Test that data is properly converted to correct types."""
# Test with different input types
int_vertices = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
int_faces = np.array([0, 1], dtype=np.int32)
mesh = LegacyGLMesh(vertices=int_vertices, faces=int_faces)
# Should be converted to float32 and uint32
self.assertEqual(mesh.vertices.dtype, np.float32)
self.assertEqual(mesh.indices.dtype, np.uint32)
[docs]
def test_initialization_array_reshaping(self):
"""Test that arrays are properly reshaped."""
# Test with 1D arrays
vertices_1d = np.array([0, 0, 0, 1, 0, 0, 0, 1, 0], dtype=np.float32)
faces_1d = np.array([0, 1, 2], dtype=np.uint32)
mesh = LegacyGLMesh(vertices=vertices_1d, faces=faces_1d)
# Should be reshaped to (N, 3) and (M,)
self.assertEqual(mesh.vertices.shape, (3, 3))
self.assertEqual(mesh.indices.shape, (3,))
[docs]
def test_from_mesh_data_with_all_attributes(self):
"""Test from_mesh_data with all MeshData attributes."""
mesh_data = MeshData(
vbo=self.test_vertices,
ebo=self.test_faces,
cbo=self.test_colors,
nbo=self.test_normals,
uvs=self.test_uvs
)
legacy_mesh = LegacyGLMesh.from_mesh_data(mesh_data)
# Test data assignments
np.testing.assert_array_equal(legacy_mesh.vertices, self.test_vertices)
np.testing.assert_array_equal(legacy_mesh.indices, self.test_faces)
np.testing.assert_array_equal(legacy_mesh.colors, self.test_colors)
np.testing.assert_array_equal(legacy_mesh.normals, self.test_normals)
np.testing.assert_array_equal(legacy_mesh.uvs, self.test_uvs)
[docs]
def test_from_mesh_data_with_minimal_attributes(self):
"""Test from_mesh_data with minimal MeshData attributes."""
mesh_data = MeshData(
vbo=self.test_vertices,
ebo=self.test_faces
)
legacy_mesh = LegacyGLMesh.from_mesh_data(mesh_data)
# Test data assignments
np.testing.assert_array_equal(legacy_mesh.vertices, self.test_vertices)
np.testing.assert_array_equal(legacy_mesh.indices, self.test_faces)
# Test default values
expected_colors = np.tile((0.0, 0.0, 1.0), (4, 1)).astype(np.float32)
np.testing.assert_array_equal(legacy_mesh.colors, expected_colors)
[docs]
def test_from_mesh_data_without_uvs(self):
"""Test from_mesh_data without UVs attribute."""
mesh_data = MeshData(
vbo=self.test_vertices,
ebo=self.test_faces,
cbo=self.test_colors,
nbo=self.test_normals
)
# Remove uvs attribute
delattr(mesh_data, 'uvs')
legacy_mesh = LegacyGLMesh.from_mesh_data(mesh_data)
# Should use default UVs
expected_uvs = np.zeros((4, 2), dtype=np.float32)
np.testing.assert_array_equal(legacy_mesh.uvs, expected_uvs)
[docs]
def test_generate_dynamic_attributes_without_uvs(self):
"""Test generate_dynamic_attributes without UVs."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
mesh.uvs = None # Ensure no UVs
attributes = mesh.generate_dynamic_attributes()
# Should have 3 attributes: positions, colors, normals
self.assertEqual(len(attributes), 3)
# Test position attribute
pos_attr = attributes[0]
self.assertEqual(pos_attr.name, "positions")
self.assertEqual(pos_attr.index, 0)
self.assertEqual(pos_attr.size, 3)
self.assertEqual(pos_attr.type, GL_FLOAT)
# Test colour attribute
color_attr = attributes[1]
self.assertEqual(color_attr.name, "colors")
self.assertEqual(color_attr.index, 1)
self.assertEqual(color_attr.size, 3)
self.assertEqual(color_attr.type, GL_FLOAT)
# Test normal attribute
normal_attr = attributes[2]
self.assertEqual(normal_attr.name, "normals")
self.assertEqual(normal_attr.index, 2)
self.assertEqual(normal_attr.size, 3)
self.assertEqual(normal_attr.type, GL_FLOAT)
[docs]
def test_generate_dynamic_attributes_with_uvs(self):
"""Test generate_dynamic_attributes with UVs."""
mesh = LegacyGLMesh(
vertices=self.test_vertices,
faces=self.test_faces,
uvs=self.test_uvs
)
attributes = mesh.generate_dynamic_attributes()
# Should have 4 attributes: positions, colors, normals, uvs
self.assertEqual(len(attributes), 4)
# Test UV attribute
uv_attr = attributes[3]
self.assertEqual(uv_attr.name, "uvs")
self.assertEqual(uv_attr.index, 3)
self.assertEqual(uv_attr.size, 2)
self.assertEqual(uv_attr.type, GL_FLOAT)
[docs]
def test_upload_first_time(self):
"""Test upload method for the first time."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock dependencies
mock_layout = MagicMock()
mock_vao = MagicMock()
with patch('picogl.renderer.legacy_glmesh.create_layout', return_value=mock_layout):
with patch('picogl.renderer.legacy_glmesh.VertexBufferGroup', return_value=mock_vao):
mesh.upload()
# Test VAO assignment
self.assertEqual(mesh.vao, mock_vao)
self.assertEqual(mesh.index_count, len(self.test_faces))
# Test VAO methods were called
mock_vao.add_vbo.assert_called()
mock_vao.add_ebo.assert_called_once()
mock_vao.set_layout.assert_called_once_with(mock_layout)
[docs]
def test_upload_already_uploaded(self):
"""Test upload method when already uploaded."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO
mock_vao = MagicMock()
mesh.vao = mock_vao
# Upload again
mesh.upload()
# Should not create new VAO
self.assertEqual(mesh.vao, mock_vao)
[docs]
def test_upload_with_uvs(self):
"""Test upload method with UVs."""
mesh = LegacyGLMesh(
vertices=self.test_vertices,
faces=self.test_faces,
uvs=self.test_uvs
)
# Mock dependencies
mock_layout = MagicMock()
mock_vao = MagicMock()
with patch('picogl.renderer.legacy_glmesh.create_layout', return_value=mock_layout):
with patch('picogl.renderer.legacy_glmesh.VertexBufferGroup', return_value=mock_vao):
mesh.upload()
# Test that UV VBO was added
calls = mock_vao.add_vbo.call_args_list
uv_call_found = any(
len(call[1]) > 0 and call[1].get('name') == 'uvs' and call[1].get('size') == 2
for call in calls
)
self.assertTrue(uv_call_found)
[docs]
def test_upload_without_uvs(self):
"""Test upload method without UVs."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
mesh.uvs = None # Ensure no UVs
# Mock dependencies
mock_layout = MagicMock()
mock_vao = MagicMock()
with patch('picogl.renderer.legacy_glmesh.create_layout', return_value=mock_layout):
with patch('picogl.renderer.legacy_glmesh.VertexBufferGroup', return_value=mock_vao):
mesh.upload()
# Test that UV VBO was not added
calls = mock_vao.add_vbo.call_args_list
uv_call_found = any(
len(call[1]) > 0 and call[1].get('name') == 'uvs'
for call in calls
)
self.assertFalse(uv_call_found)
[docs]
def test_upload_vbo_parameters(self):
"""Test that VBOs are added with correct parameters."""
mesh = LegacyGLMesh(
vertices=self.test_vertices,
faces=self.test_faces,
colors=self.test_colors,
normals=self.test_normals,
uvs=self.test_uvs
)
# Mock dependencies
mock_layout = MagicMock()
mock_vao = MagicMock()
with patch('picogl.renderer.legacy_glmesh.create_layout', return_value=mock_layout):
with patch('picogl.renderer.legacy_glmesh.VertexBufferGroup', return_value=mock_vao):
mesh.upload()
# Test VBO calls
calls = mock_vao.add_vbo.call_args_list
# Should have 4 VBO calls (vertices, colors, normals, uvs)
self.assertEqual(len(calls), 4)
# Test vertex VBO (name "vbo", size 3)
vertex_call = next(call for call in calls if call[1].get('name') == 'vbo')
self.assertEqual(vertex_call[1]['size'], 3)
np.testing.assert_array_equal(vertex_call[1]['data'], self.test_vertices)
# Test colour VBO (name "cbo", size 3)
color_call = next(call for call in calls if call[1].get('name') == 'cbo')
self.assertEqual(color_call[1]['size'], 3)
np.testing.assert_array_equal(color_call[1]['data'], self.test_colors)
# Test normal VBO (name "nbo", size 3)
normal_call = next(call for call in calls if call[1].get('name') == 'nbo')
self.assertEqual(normal_call[1]['size'], 3)
np.testing.assert_array_equal(normal_call[1]['data'], self.test_normals)
# Test UV VBO (name "uvs", size 2)
uv_call = next(call for call in calls if call[1].get('name') == 'uvs')
self.assertEqual(uv_call[1]['size'], 2)
np.testing.assert_array_equal(uv_call[1]['data'], self.test_uvs)
[docs]
def test_ebo_parameters(self):
"""Test that EBO is added with correct parameters."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock dependencies
mock_layout = MagicMock()
mock_vao = MagicMock()
with patch('picogl.renderer.legacy_glmesh.create_layout', return_value=mock_layout):
with patch('picogl.renderer.legacy_glmesh.VertexBufferGroup', return_value=mock_vao):
mesh.upload()
# Test EBO call
mock_vao.add_ebo.assert_called_once()
# Test the data parameter separately to avoid array comparison issues
call_args = mock_vao.add_ebo.call_args
np.testing.assert_array_equal(call_args[1]['data'], self.test_faces)
[docs]
def test_bind_with_uploaded_mesh(self):
"""Test bind method with uploaded mesh."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO
mock_vao = MagicMock()
mesh.vao = mock_vao
mesh.bind()
# Test VAO context manager was called
mock_vao.__enter__.assert_called_once()
[docs]
def test_bind_without_upload(self):
"""Test bind method without upload raises error."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
with self.assertRaises(RuntimeError) as context:
mesh.bind()
self.assertIn("GLMesh not uploaded", str(context.exception))
[docs]
def test_unbind_with_vao(self):
"""Test unbind method with VAO."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO
mock_vao = MagicMock()
mesh.vao = mock_vao
mesh.unbind()
# Test VAO context manager exit was called
mock_vao.__exit__.assert_called_once_with(None, None, None)
[docs]
def test_unbind_without_vao(self):
"""Test unbind method without VAO."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Should not raise error
mesh.unbind()
[docs]
def test_delete_with_vao(self):
"""Test delete method with VAO."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO
mock_vao = MagicMock()
mesh.vao = mock_vao
mesh.index_count = 10
mesh.delete()
# Test VAO was deleted and state was reset
self.assertIsNone(mesh.vao)
self.assertEqual(mesh.index_count, 0)
[docs]
def test_delete_without_vao(self):
"""Test delete method without VAO."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Should not raise error
mesh.delete()
[docs]
def test_context_manager(self):
"""Test that LegacyGLMesh can be used as a context manager."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO
mock_vao = MagicMock()
mesh.vao = mock_vao
# Test context manager usage
with mesh as context_mesh:
self.assertEqual(context_mesh, mesh)
mock_vao.__enter__.assert_called_once()
mock_vao.__exit__.assert_called_once_with(None, None, None)
[docs]
def test_draw_with_uploaded_mesh(self):
"""Test draw method with uploaded mesh."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO and its VBOs
mock_vao = MagicMock()
mock_vbo = MagicMock()
mock_cbo = MagicMock()
mock_ebo = MagicMock()
mock_vao.vbo = mock_vbo
mock_vao.cbo = mock_cbo
mock_vao.ebo = mock_ebo
mesh.vao = mock_vao
mesh.index_count = len(self.test_faces)
with patch('picogl.renderer.legacy_glmesh.glDrawElements') as mock_draw:
with patch('picogl.renderer.legacy_glmesh.legacy_client_states') as mock_client_states:
mock_client_states.return_value.__enter__ = MagicMock()
mock_client_states.return_value.__exit__ = MagicMock()
mesh.draw()
# Test draw call was made
mock_draw.assert_called_once_with(
GL_TRIANGLES,
len(self.test_faces),
GL_UNSIGNED_INT,
mock_draw.call_args[0][3] # ctypes.c_void_p(0)
)
# Test legacy client states were used
mock_client_states.assert_called_once_with(GL_VERTEX_ARRAY, GL_COLOR_ARRAY)
[docs]
def test_draw_with_custom_mode(self):
"""Test draw method with custom drawing mode."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO and its VBOs
mock_vao = MagicMock()
mock_vbo = MagicMock()
mock_cbo = MagicMock()
mock_ebo = MagicMock()
mock_vao.vbo = mock_vbo
mock_vao.cbo = mock_cbo
mock_vao.ebo = mock_ebo
mesh.vao = mock_vao
mesh.index_count = len(self.test_faces)
with patch('picogl.renderer.legacy_glmesh.glDrawElements') as mock_draw:
with patch('picogl.renderer.legacy_glmesh.legacy_client_states') as mock_client_states:
mock_client_states.return_value.__enter__ = MagicMock()
mock_client_states.return_value.__exit__ = MagicMock()
mesh.draw(mode=GL_TRIANGLES)
# Test draw call was made with custom mode
mock_draw.assert_called_once()
[docs]
def test_draw_without_upload(self):
"""Test draw method without upload handles error."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# The draw method catches exceptions and prints them instead of raising
with patch('builtins.print') as mock_print:
mesh.draw()
# Should print the error message
mock_print.assert_called_once()
[docs]
def test_draw_with_exception_handling(self):
"""Test draw method with exception handling."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO and its VBOs that raise exception
mock_vao = MagicMock()
mock_vbo = MagicMock()
mock_cbo = MagicMock()
mock_ebo = MagicMock()
mock_vao.vbo = mock_vbo
mock_vao.cbo = mock_cbo
mock_vao.ebo = mock_ebo
# Make the legacy_client_states context manager raise an exception
with patch('picogl.renderer.legacy_glmesh.legacy_client_states') as mock_client_states:
mock_client_states.return_value.__enter__.side_effect = Exception("OpenGL error")
mock_client_states.return_value.__exit__ = MagicMock()
mesh.vao = mock_vao
mesh.index_count = len(self.test_faces)
# Should not raise exception, but print it
with patch('builtins.print') as mock_print:
mesh.draw()
mock_print.assert_called_once()
[docs]
def test_vertex_count_calculation(self):
"""Test vertex count calculation."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Test initial count
self.assertEqual(mesh.index_count, 0)
# Test after upload
mock_layout = MagicMock()
mock_vao = MagicMock()
with patch('picogl.renderer.legacy_glmesh.create_layout', return_value=mock_layout):
with patch('picogl.renderer.legacy_glmesh.VertexBufferGroup', return_value=mock_vao):
mesh.upload()
self.assertEqual(mesh.index_count, len(self.test_faces))
[docs]
def test_data_validation(self):
"""Test data validation and type checking."""
# Test with valid data
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
self.assertEqual(mesh.vertices.shape[1], 3) # Should be (N, 3)
self.assertEqual(mesh.indices.ndim, 1) # Should be 1D
# Test with different vertex counts
vertices_2 = np.array([[0, 0, 0], [1, 1, 1]], dtype=np.float32)
faces_2 = np.array([0, 1], dtype=np.uint32)
mesh_2 = LegacyGLMesh(vertices=vertices_2, faces=faces_2)
self.assertEqual(mesh_2.vertices.shape[0], 2)
self.assertEqual(mesh_2.colors.shape[0], 2) # Colors should match vertex count
[docs]
def test_default_color_generation(self):
"""Test default colour generation."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Should generate blue colors for all vertices
expected_colors = np.tile((0.0, 0.0, 1.0), (4, 1)).astype(np.float32)
np.testing.assert_array_equal(mesh.colors, expected_colors)
[docs]
def test_default_normal_generation(self):
"""Test default normal generation."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Should generate zero normals
expected_normals = np.zeros_like(self.test_vertices)
np.testing.assert_array_equal(mesh.normals, expected_normals)
[docs]
def test_default_uv_generation(self):
"""Test default UV generation."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Should generate zero UVs
expected_uvs = np.zeros((4, 2), dtype=np.float32)
np.testing.assert_array_equal(mesh.uvs, expected_uvs)
[docs]
def test_mesh_data_integration(self):
"""Test integration with MeshData class."""
# Create MeshData
mesh_data = MeshData.from_raw(
vertices=self.test_vertices,
indices=self.test_faces,
colors=self.test_colors,
normals=self.test_normals,
uvs=self.test_uvs
)
# Create LegacyGLMesh from MeshData
legacy_mesh = LegacyGLMesh.from_mesh_data(mesh_data)
# Test data consistency - LegacyGLMesh reshapes data to (N, 3) while MeshData keeps it flat
np.testing.assert_array_equal(legacy_mesh.vertices.reshape(-1), mesh_data.vbo)
np.testing.assert_array_equal(legacy_mesh.indices, mesh_data.ebo)
np.testing.assert_array_equal(legacy_mesh.colors.reshape(-1), mesh_data.cbo)
np.testing.assert_array_equal(legacy_mesh.normals.reshape(-1), mesh_data.nbo)
np.testing.assert_array_equal(legacy_mesh.uvs.reshape(-1), mesh_data.uvs)
[docs]
def test_legacy_client_states_integration(self):
"""Test integration with legacy client states."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
# Mock VAO and its VBOs
mock_vao = MagicMock()
mock_vbo = MagicMock()
mock_cbo = MagicMock()
mock_ebo = MagicMock()
mock_vao.vbo = mock_vbo
mock_vao.cbo = mock_cbo
mock_vao.ebo = mock_ebo
mesh.vao = mock_vao
mesh.index_count = len(self.test_faces)
with patch('picogl.renderer.legacy_glmesh.legacy_client_states') as mock_client_states:
mock_client_states.return_value.__enter__ = MagicMock()
mock_client_states.return_value.__exit__ = MagicMock()
mesh.draw()
# Verify legacy client states were called with correct parameters
mock_client_states.assert_called_once_with(GL_VERTEX_ARRAY, GL_COLOR_ARRAY)
[docs]
def test_attribute_spec_properties(self):
"""Test that generated attributes have correct properties."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
attributes = mesh.generate_dynamic_attributes()
for attr in attributes:
# All attributes should have required properties
self.assertIsNotNone(attr.name)
self.assertIsInstance(attr.index, int)
self.assertIsInstance(attr.size, int)
self.assertEqual(attr.type, GL_FLOAT)
self.assertFalse(attr.normalized)
self.assertEqual(attr.stride, 0)
self.assertEqual(attr.offset, 0)
[docs]
def test_upload_layout_creation(self):
"""Test that layout is created with correct attributes."""
mesh = LegacyGLMesh(vertices=self.test_vertices, faces=self.test_faces)
with patch('picogl.renderer.legacy_glmesh.create_layout') as mock_create_layout:
with patch('picogl.renderer.legacy_glmesh.VertexBufferGroup'):
mesh.upload()
# Test that create_layout was called
mock_create_layout.assert_called_once()
# Test that the attributes passed to create_layout are correct
call_args = mock_create_layout.call_args[0][0] # First positional argument
# Should have 4 attributes: positions, colors, normals, uvs (UVs are always included)
self.assertEqual(len(call_args), 4)
# Test attribute names
attr_names = [attr.name for attr in call_args]
self.assertIn("positions", attr_names)
self.assertIn("colors", attr_names)
self.assertIn("normals", attr_names)
self.assertIn("uvs", attr_names)
if __name__ == "__main__":
unittest.main()