Fix tests for CI?
Python CI / test (push) Failing after 7m45s

This commit is contained in:
2025-11-04 22:30:04 +01:00
parent 9ba35d2fa8
commit 37505d3dcc
5 changed files with 148 additions and 31 deletions
+64 -23
View File
@@ -28,6 +28,11 @@ class TestDocumentLayouter:
self.mock_page.can_fit_line = Mock(return_value=True)
self.mock_page.add_child = Mock()
# Create mock page style with all required numeric properties
self.mock_page.style = Mock()
self.mock_page.style.max_font_size = 72 # Reasonable maximum font size
self.mock_page.style.line_spacing_multiplier = 1.2 # Standard line spacing
# Create mock style resolver
self.mock_style_resolver = Mock()
self.mock_page.style_resolver = self.mock_style_resolver
@@ -51,12 +56,20 @@ class TestDocumentLayouter:
self.mock_concrete_style.word_spacing_max = 8.0
self.mock_concrete_style.text_align = "left"
# Create mock font that returns proper metrics
# Create mock font that returns proper numeric metrics (not Mock objects)
mock_font = Mock()
mock_font.getmetrics.return_value = (12, 4) # (ascent, descent)
# CRITICAL: getmetrics() must return actual numeric values, not Mock objects
# This prevents "TypeError: '>' not supported between instances of 'Mock' and 'Mock'"
mock_font.getmetrics.return_value = (12, 4) # (ascent, descent) as actual integers
mock_font.font = mock_font # For accessing .font property
self.mock_concrete_style.create_font = Mock()
# Create mock font object that can be used by create_font
mock_font_instance = Mock()
mock_font_instance.font = mock_font
mock_font_instance.font_size = 16
mock_font_instance.colour = (0, 0, 0)
mock_font_instance.background = (255, 255, 255, 0)
self.mock_concrete_style.create_font = Mock(return_value=mock_font_instance)
# Update mock words to have proper style with font
for word in self.mock_words:
@@ -66,11 +79,15 @@ class TestDocumentLayouter:
word.style.colour = (0, 0, 0)
word.style.background = None
@patch('pyWebLayout.layout.document_layouter.StyleResolver')
@patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry')
@patch('pyWebLayout.layout.document_layouter.Line')
def test_paragraph_layouter_basic_flow(self, mock_line_class, mock_style_registry_class):
def test_paragraph_layouter_basic_flow(self, mock_line_class, mock_style_registry_class, mock_style_resolver_class):
"""Test basic paragraph layouter functionality."""
# Setup mocks
# Setup mocks for StyleResolver and ConcreteStyleRegistry
mock_style_resolver = Mock()
mock_style_resolver_class.return_value = mock_style_resolver
mock_style_registry = Mock()
mock_style_registry_class.return_value = mock_style_registry
mock_style_registry.get_concrete_style.return_value = self.mock_concrete_style
@@ -88,8 +105,9 @@ class TestDocumentLayouter:
assert failed_word_index is None
assert remaining_pretext is None
# Verify style registry was used correctly
mock_style_registry_class.assert_called_once_with(self.mock_page.style_resolver)
# Verify StyleResolver and ConcreteStyleRegistry were created correctly
mock_style_resolver_class.assert_called_once()
mock_style_registry_class.assert_called_once_with(mock_style_resolver)
mock_style_registry.get_concrete_style.assert_called_once_with(self.mock_paragraph.style)
# Verify Line was created with correct spacing constraints
@@ -98,16 +116,27 @@ class TestDocumentLayouter:
call_args = mock_line_class.call_args
assert call_args[1]['spacing'] == expected_spacing
@patch('pyWebLayout.layout.document_layouter.StyleResolver')
@patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry')
@patch('pyWebLayout.layout.document_layouter.Line')
def test_paragraph_layouter_word_spacing_constraints_extraction(self, mock_line_class, mock_style_registry_class):
def test_paragraph_layouter_word_spacing_constraints_extraction(self, mock_line_class, mock_style_registry_class, mock_style_resolver_class):
"""Test that word spacing constraints are correctly extracted from style."""
# Create concrete style with specific constraints
concrete_style = Mock()
concrete_style.word_spacing_min = 5.5
concrete_style.word_spacing_max = 15.2
concrete_style.text_align = "justify"
concrete_style.create_font = Mock()
# Create a mock font that concrete_style.create_font returns
mock_font = Mock()
mock_font.font = Mock()
mock_font.font.getmetrics.return_value = (12, 4)
mock_font.font_size = 16
concrete_style.create_font = Mock(return_value=mock_font)
# Setup StyleResolver and ConcreteStyleRegistry mocks
mock_style_resolver = Mock()
mock_style_resolver_class.return_value = mock_style_resolver
mock_style_registry = Mock()
mock_style_registry_class.return_value = mock_style_registry
@@ -252,39 +281,51 @@ class TestDocumentLayouter:
)
assert result == (True, None, None)
@patch('pyWebLayout.layout.document_layouter.paragraph_layouter')
def test_document_layouter_layout_document_success(self, mock_paragraph_layouter):
def test_document_layouter_layout_document_success(self):
"""Test DocumentLayouter.layout_document with successful layout."""
mock_paragraph_layouter.return_value = (True, None, None)
from pyWebLayout.abstract import Paragraph
paragraphs = [self.mock_paragraph, Mock(), Mock()]
# Create Mock paragraphs that pass isinstance checks
paragraphs = [
Mock(spec=Paragraph),
Mock(spec=Paragraph),
Mock(spec=Paragraph)
]
with patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry'):
layouter = DocumentLayouter(self.mock_page)
# Mock the layout_paragraph method to return success
layouter.layout_paragraph = Mock(return_value=(True, None, None))
result = layouter.layout_document(paragraphs)
assert result is True
assert mock_paragraph_layouter.call_count == 3
assert layouter.layout_paragraph.call_count == 3
@patch('pyWebLayout.layout.document_layouter.paragraph_layouter')
def test_document_layouter_layout_document_failure(self, mock_paragraph_layouter):
def test_document_layouter_layout_document_failure(self):
"""Test DocumentLayouter.layout_document with layout failure."""
# First paragraph succeeds, second fails
mock_paragraph_layouter.side_effect = [
(True, None, None), # First paragraph succeeds
(False, 3, None), # Second paragraph fails
]
from pyWebLayout.abstract import Paragraph
paragraphs = [self.mock_paragraph, Mock()]
# Create Mock paragraphs that pass isinstance checks
paragraphs = [
Mock(spec=Paragraph),
Mock(spec=Paragraph)
]
with patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry'):
layouter = DocumentLayouter(self.mock_page)
# Mock the layout_paragraph method: first succeeds, second fails
layouter.layout_paragraph = Mock(side_effect=[
(True, None, None), # First paragraph succeeds
(False, 3, None), # Second paragraph fails
])
result = layouter.layout_document(paragraphs)
assert result is False
assert mock_paragraph_layouter.call_count == 2
assert layouter.layout_paragraph.call_count == 2
def test_real_style_integration(self):
"""Test integration with real style system."""
@@ -11,6 +11,8 @@ from unittest.mock import Mock, patch
from PIL import Image, ImageDraw
import numpy as np
from typing import List, Optional
import os
import logging
from pyWebLayout.layout.document_layouter import paragraph_layouter, DocumentLayouter
from pyWebLayout.style.abstract_style import AbstractStyle
@@ -21,13 +23,42 @@ from pyWebLayout.concrete.page import Page
from pyWebLayout.concrete.text import Line, Text
from pyWebLayout.abstract.inline import Word
# Enable logging to see font loading messages
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def verify_bundled_font_available():
"""Verify that the bundled font is available for integration tests."""
# Get the bundled font path
current_dir = os.path.dirname(os.path.abspath(__file__))
# Navigate up to pyWebLayout root, then to assets/fonts
project_root = os.path.dirname(os.path.dirname(current_dir))
bundled_font_path = os.path.join(project_root, 'pyWebLayout', 'assets', 'fonts', 'DejaVuSans.ttf')
logger.info(f"Integration tests checking for bundled font at: {bundled_font_path}")
if not os.path.exists(bundled_font_path):
pytest.fail(
f"INTEGRATION TEST FAILURE: Bundled font not found at {bundled_font_path}\n"
f"Integration tests require the bundled font to ensure consistent behavior.\n"
f"This likely means the font was not included in the package build."
)
logger.info(f"Bundled font found at: {bundled_font_path}")
return bundled_font_path
class MockWord(Word):
"""A simple mock word that extends the real Word class."""
def __init__(self, text, style=None):
if style is None:
# Integration tests MUST use the bundled font for consistency
style = Font(font_size=16)
# Verify the font loaded properly
if style.font.path is None:
logger.warning("Font loaded without explicit path - may be using PIL default")
# Initialize the base Word with required parameters
super().__init__(text, style)
self._concrete_texts = []
@@ -73,6 +104,11 @@ class MockParagraph:
class TestDocumentLayouterIntegration:
"""Integration tests using real components."""
@classmethod
def setup_class(cls):
"""Verify bundled font is available before running any tests."""
verify_bundled_font_available()
def test_single_page_layout_with_real_components(self):
"""Test layout on a single page using real Line and Text objects."""
# Create a real page that can fit content