""" Unit tests for build system code minification features. These tests verify that the build process can: 1. Strip comments while preserving functionality 2. Remove docstrings except bl_info and functionally required ones 3. Optimize whitespace 4. Maintain valid Python syntax and runtime behavior """ import ast import textwrap from pathlib import Path import sys # Add scripts directory to path to import build_addon sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts")) import pytest def test_strips_inline_comments(): """Test that inline comments are removed from code.""" source = textwrap.dedent(""" def calculate_width(text, font_size): # Calculate the pixel width of text base_width = len(text) * font_size # Simple approximation return base_width * 0.6 # Adjustment factor """).strip() from build_addon import minify_python_code result = minify_python_code(source) # Verify comments are removed assert "# Calculate" not in result assert "# Simple approximation" not in result assert "# Adjustment factor" not in result # Verify functionality is preserved assert "def calculate_width(text, font_size):" in result assert "base_width = len(text) * font_size" in result assert "return base_width * 0.6" in result # Verify result is valid Python ast.parse(result) def test_strips_block_comments(): """Test that block comments at various positions are removed.""" source = textwrap.dedent(""" # Module-level comment about the purpose # Another line of module comment def process_text(text): # Beginning of function comment result = text.upper() # Middle comment result = result.strip() # End comment return result """).strip() from build_addon import minify_python_code result = minify_python_code(source) # All comments should be gone assert "# Module-level" not in result assert "# Another line" not in result assert "# Beginning of function" not in result assert "# Middle comment" not in result assert "# End comment" not in result # But code should remain assert "def process_text(text):" in result assert "result = text.upper()" in result assert "result = result.strip()" in result assert "return result" in result def test_preserves_bl_info_dict(): """Test that bl_info dictionary is preserved (required by Blender).""" source = textwrap.dedent(''' """ Addon docstring that should be removed. """ bl_info = { "name": "Text Texture Generator", "author": "Developer", "version": (1, 0, 0), "blender": (4, 0, 0), "description": "Generate text textures", } def some_function(): """Regular docstring to remove.""" return "value" ''').strip() from build_addon import minify_python_code result = minify_python_code(source) # bl_info must be preserved exactly assert "bl_info = {" in result assert '"name": "Text Texture Generator"' in result assert '"author": "Developer"' in result assert '"version": (1, 0, 0)' in result # Module docstring should be removed assert "Addon docstring that should be removed" not in result # Function docstring should be removed assert "Regular docstring to remove" not in result # Code should remain assert "def some_function():" in result assert 'return "value"' in result def test_removes_function_docstrings(): """Test that function and method docstrings are removed.""" source = textwrap.dedent(''' class TextGenerator: """Class docstring to remove.""" def generate(self, text): """ Generate a texture from text. Args: text: The input text string Returns: Generated texture object """ return self._process(text) def _process(self, text): """Internal processing method.""" return text.upper() ''').strip() from build_addon import minify_python_code result = minify_python_code(source) # All docstrings should be removed assert "Class docstring to remove" not in result assert "Generate a texture from text" not in result assert "Args:" not in result assert "Returns:" not in result assert "Internal processing method" not in result # Code structure should remain assert "class TextGenerator:" in result assert "def generate(self, text):" in result assert "return self._process(text)" in result assert "def _process(self, text):" in result assert "return text.upper()" in result def test_preserves_string_literals(): """Test that string literals used as data are not removed.""" source = textwrap.dedent(''' def get_error_messages(): """Function docstring to remove.""" # Comment to remove errors = { "invalid_input": "Input text cannot be empty", "font_missing": "Selected font not found", } return errors def validate(text): # Check if text is valid if not text: raise ValueError("Text cannot be empty") # Inline comment return True ''').strip() from build_addon import minify_python_code result = minify_python_code(source) # Docstrings and comments should be removed assert "Function docstring to remove" not in result assert "# Comment to remove" not in result assert "# Check if text is valid" not in result assert "# Inline comment" not in result # String literals in code must be preserved assert '"invalid_input": "Input text cannot be empty"' in result assert '"font_missing": "Selected font not found"' in result assert 'raise ValueError("Text cannot be empty")' in result # Code structure preserved assert "def get_error_messages():" in result assert "def validate(text):" in result def test_optimizes_whitespace(): """Test that excessive blank lines and inconsistent indentation are cleaned up.""" source = textwrap.dedent(""" def function_one(): result = 1 return result def function_two(): # Comment value = 2 return value class MyClass: def method(self): pass """).strip() from build_addon import minify_python_code result = minify_python_code(source) # Should not have excessive blank lines (max 1 between definitions) assert "\n\n\n" not in result # Functions should be separated by at most one blank line lines = result.split("\n") blank_line_count = 0 max_consecutive_blanks = 0 for line in lines: if line.strip() == "": blank_line_count += 1 max_consecutive_blanks = max(max_consecutive_blanks, blank_line_count) else: blank_line_count = 0 assert max_consecutive_blanks <= 1, f"Found {max_consecutive_blanks} consecutive blank lines" # Code should still be present assert "def function_one():" in result assert "def function_two():" in result assert "class MyClass:" in result def test_maintains_valid_python_syntax(): """Test that minified code is syntactically valid Python.""" source = textwrap.dedent(''' """Module docstring.""" # Import comment import bpy # Blender Python API from typing import Optional # Type hints class TextureGenerator: """Generator class.""" def __init__(self, resolution=1024): """Initialize generator.""" # Store resolution self.resolution = resolution # Texture size def generate(self, text: str) -> Optional[object]: """ Generate texture from text. Args: text: Input text string Returns: Texture object or None """ if not text: # Validate input return None # Create texture texture = bpy.data.images.new( "TextTexture", self.resolution, self.resolution ) return texture # Return result ''').strip() from build_addon import minify_python_code result = minify_python_code(source) # Must be valid Python try: ast.parse(result) except SyntaxError as e: pytest.fail(f"Minified code has syntax error: {e}") # Should not have docstrings or comments assert '"""Module docstring."""' not in result assert '"""Generator class."""' not in result assert '"""Initialize generator."""' not in result assert "# Import comment" not in result assert "# Blender Python API" not in result assert "# Store resolution" not in result # Code should be present assert "import bpy" in result assert "from typing import Optional" in result assert "class TextureGenerator:" in result assert "def __init__(self, resolution=1024):" in result assert "self.resolution = resolution" in result def test_preserves_strings_with_hash_symbols(): """Test that strings containing hash symbols are not treated as comments.""" source = textwrap.dedent(''' def create_id(text): """Generate hash ID.""" # Function comment # Create the ID with a hash prefix return f"#{hash(text)}" # Hash prefix def parse_color(color_string): # Check if it's a hex color if color_string.startswith("#"): # Hex colors start with # return color_string return None ''').strip() from build_addon import minify_python_code result = minify_python_code(source) # Comments should be removed assert "Function comment" not in result assert "# Create the ID" not in result assert "# Hash prefix" not in result assert "# Check if it's a hex color" not in result assert "# Hex colors start with #" not in result # String literals with # should be preserved assert 'f"#{hash(text)}"' in result or 'f"#{hash(text)}"' in result assert 'startswith("#")' in result or 'startswith("#")' in result # Code structure preserved assert "def create_id(text):" in result assert "def parse_color(color_string):" in result def test_preserves_functionality_complex_example(): """Test minification preserves functionality in complex real-world code.""" source = textwrap.dedent(''' """ Text processing module for the addon. This module handles text wrapping and fitting. """ import re # Regular expressions from typing import List, Tuple # Type hints class TextProcessor: """Process text for texture generation.""" def __init__(self, max_width: int = 100): """ Initialize processor. Args: max_width: Maximum line width in pixels """ # Store configuration self.max_width = max_width # Width limit self._cache = {} # Results cache def wrap_text(self, text: str) -> List[str]: """ Wrap text to fit within max width. Args: text: Input text to wrap Returns: List of wrapped lines """ # Check cache first if text in self._cache: # Cache hit return self._cache[text] # Split into words words = re.split(r'\\s+', text) # Word boundaries lines = [] current_line = "" # Process each word for word in words: # Check if adding word exceeds width test_line = f"{current_line} {word}".strip() if len(test_line) <= self.max_width: # Fits current_line = test_line else: # Doesn't fit if current_line: # Save current line lines.append(current_line) current_line = word # Start new line # Add final line if current_line: # Not empty lines.append(current_line) # Cache result self._cache[text] = lines # Store for reuse return lines ''').strip() from build_addon import minify_python_code result = minify_python_code(source) # Must be valid Python try: compiled = compile(result, '', 'exec') except SyntaxError as e: pytest.fail(f"Minified code won't compile: {e}") # Verify it's executable (can define the class) namespace = {} exec(compiled, namespace) assert 'TextProcessor' in namespace # Verify functionality works processor = namespace['TextProcessor'](max_width=20) result_lines = processor.wrap_text("hello world test") assert isinstance(result_lines, list) assert len(result_lines) > 0 # No docstrings or comments should remain minified_lines = result.split('\n') for line in minified_lines: stripped = line.strip() if stripped: assert not stripped.startswith('#'), f"Found comment line: {line}" assert '"""' not in line, f"Found docstring in: {line}"