Files
Text-Texture-Generator-for-…/tests/unit/test_build_minification.py
2025-10-10 14:41:18 +02:00

442 lines
14 KiB
Python

"""
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, '<minified>', '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}"