Source code for picogl.tests.test_legacy_glmesh

"""
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()