|
|
|
""" |
|
Comprehensive Test Suite for Integrated Crossword Generator |
|
|
|
Tests the complete integration between thematic word discovery and API clue generation, |
|
ensuring the system works correctly and produces high-quality results. |
|
|
|
This test suite uses pre-cached embeddings and vocabulary files (50K words) from |
|
model_cache/ directory for faster test execution, avoiding re-initialization of |
|
the sentence transformer model and vocabulary generation. |
|
|
|
Performance: ~93s initialization with cache vs ~250s without cache (~2.7x faster) |
|
|
|
To verify cache setup before running tests: |
|
python verify_cached_tests.py |
|
|
|
To run the full test suite: |
|
export HF_TOKEN='your_token' && python test_integrated_system.py |
|
""" |
|
|
|
import sys |
|
import os |
|
import time |
|
import unittest |
|
from pathlib import Path |
|
from unittest.mock import Mock, patch |
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent)) |
|
|
|
try: |
|
from integrated_crossword_generator import IntegratedCrosswordGenerator, CrosswordEntry |
|
INTEGRATED_AVAILABLE = True |
|
except ImportError as e: |
|
print(f"❌ Integration import error: {e}") |
|
INTEGRATED_AVAILABLE = False |
|
|
|
|
|
class TestIntegratedCrosswordGenerator(unittest.TestCase): |
|
"""Test cases for the integrated crossword generator.""" |
|
|
|
@classmethod |
|
def setUpClass(cls): |
|
"""Set up test environment.""" |
|
if not INTEGRATED_AVAILABLE: |
|
cls.skipTest(cls, "Integrated generator not available") |
|
|
|
|
|
cls.test_token = os.getenv('HF_TOKEN') |
|
if not cls.test_token: |
|
print("⚠️ HF_TOKEN not set - some tests may be skipped") |
|
|
|
def setUp(self): |
|
"""Set up each test.""" |
|
|
|
cache_dir = str(Path(__file__).parent / 'model_cache') |
|
self.generator = IntegratedCrosswordGenerator( |
|
vocab_size_limit=50000, |
|
cache_dir=cache_dir |
|
) |
|
|
|
def test_initialization(self): |
|
"""Test generator initialization.""" |
|
self.assertFalse(self.generator.is_initialized) |
|
|
|
|
|
start_time = time.time() |
|
self.generator.initialize() |
|
init_time = time.time() - start_time |
|
|
|
self.assertTrue(self.generator.is_initialized) |
|
|
|
|
|
system_info = self.generator.get_system_info() |
|
self.assertIn('components', system_info) |
|
self.assertIn('stats', system_info) |
|
|
|
|
|
|
|
self.assertLess(init_time, 120.0, "Initialization should complete within 2 minutes with cached files") |
|
|
|
|
|
if self.generator.thematic_ready: |
|
vocab_size = self.generator.thematic_generator.get_vocabulary_size() |
|
self.assertEqual(vocab_size, 50000, "Should use full 50K cached vocabulary") |
|
|
|
def test_cached_files_usage(self): |
|
"""Test that cached vocabulary and embeddings are being used.""" |
|
cache_dir = Path(self.generator.cache_dir) |
|
|
|
|
|
vocab_file = cache_dir / "unified_vocabulary_50000.pkl" |
|
freq_file = cache_dir / "unified_frequencies_50000.pkl" |
|
embeddings_file = cache_dir / "unified_embeddings_all-mpnet-base-v2_50000.npy" |
|
|
|
self.assertTrue(vocab_file.exists(), f"Vocabulary cache file should exist: {vocab_file}") |
|
self.assertTrue(freq_file.exists(), f"Frequency cache file should exist: {freq_file}") |
|
self.assertTrue(embeddings_file.exists(), f"Embeddings cache file should exist: {embeddings_file}") |
|
|
|
|
|
self.generator.initialize() |
|
|
|
if self.generator.thematic_ready: |
|
vocab_size = self.generator.thematic_generator.get_vocabulary_size() |
|
self.assertEqual(vocab_size, 50000, "Should use cached 50K vocabulary") |
|
|
|
|
|
self.assertIsNotNone(self.generator.thematic_generator.vocab_embeddings) |
|
embeddings_shape = self.generator.thematic_generator.vocab_embeddings.shape |
|
self.assertEqual(embeddings_shape[0], 50000, "Embeddings should have 50K entries") |
|
self.assertEqual(embeddings_shape[1], 768, "Should use all-mpnet-base-v2 embeddings (768 dims)") |
|
|
|
def test_component_availability(self): |
|
"""Test availability of required components.""" |
|
self.generator.initialize() |
|
|
|
|
|
has_thematic = self.generator.thematic_ready |
|
has_api = self.generator.api_ready |
|
|
|
self.assertTrue(has_thematic or has_api, "At least one component should be available") |
|
|
|
if has_thematic: |
|
self.assertIsNotNone(self.generator.thematic_generator) |
|
vocab_size = self.generator.thematic_generator.get_vocabulary_size() |
|
self.assertGreater(vocab_size, 0) |
|
|
|
if has_api: |
|
self.assertIsNotNone(self.generator.api_clue_generator) |
|
|
|
def test_word_discovery_only(self): |
|
"""Test word discovery when only thematic generator is available.""" |
|
self.generator.initialize() |
|
|
|
if not self.generator.thematic_ready: |
|
self.skipTest("Thematic generator not available") |
|
|
|
|
|
self.generator.api_ready = False |
|
|
|
|
|
words = self.generator._discover_words("animals", 5, "medium", 0.3) |
|
|
|
if words: |
|
self.assertIsInstance(words, list) |
|
for word, similarity, tier in words: |
|
self.assertIsInstance(word, str) |
|
self.assertIsInstance(similarity, float) |
|
self.assertIsInstance(tier, str) |
|
self.assertGreater(len(word), 2) |
|
self.assertGreaterEqual(similarity, 0.0) |
|
|
|
def test_api_clue_generation_only(self): |
|
"""Test API clue generation when only API generator is available.""" |
|
if not self.test_token: |
|
self.skipTest("HF_TOKEN not available for API testing") |
|
|
|
self.generator.initialize() |
|
|
|
if not self.generator.api_ready: |
|
self.skipTest("API generator not available") |
|
|
|
|
|
self.generator.thematic_ready = False |
|
|
|
|
|
mock_words = [("CAT", 0.8, "tier_5_common"), ("DOG", 0.7, "tier_4_highly_common")] |
|
|
|
entries = self.generator._generate_clues_for_words(mock_words, "animals") |
|
|
|
self.assertIsInstance(entries, list) |
|
for entry in entries: |
|
self.assertIsInstance(entry, CrosswordEntry) |
|
self.assertIsInstance(entry.word, str) |
|
self.assertIsInstance(entry.clue, str) |
|
self.assertGreater(len(entry.clue), 5) |
|
|
|
def test_full_integration(self): |
|
"""Test complete integration when both components are available.""" |
|
self.generator.initialize() |
|
|
|
if not (self.generator.thematic_ready and self.generator.api_ready): |
|
self.skipTest("Full integration requires both components") |
|
|
|
|
|
entries = self.generator.generate_crossword_entries( |
|
topic="animals", |
|
num_words=3, |
|
difficulty="medium" |
|
) |
|
|
|
self.assertIsInstance(entries, list) |
|
self.assertLessEqual(len(entries), 3) |
|
|
|
for entry in entries: |
|
self.assertIsInstance(entry, CrosswordEntry) |
|
self.assertIsInstance(entry.word, str) |
|
self.assertIsInstance(entry.clue, str) |
|
self.assertEqual(entry.topic, "animals") |
|
self.assertGreater(entry.similarity_score, 0.0) |
|
self.assertIn("tier_", entry.frequency_tier) |
|
|
|
def test_difficulty_filtering(self): |
|
"""Test difficulty-based word filtering.""" |
|
self.generator.initialize() |
|
|
|
if not self.generator.thematic_ready: |
|
self.skipTest("Requires thematic generator for difficulty testing") |
|
|
|
|
|
difficulties = ["easy", "medium", "hard"] |
|
|
|
for difficulty in difficulties: |
|
with self.subTest(difficulty=difficulty): |
|
mock_results = [ |
|
("CAT", 0.8, "tier_3_very_common"), |
|
("ALGORITHM", 0.7, "tier_8_uncommon"), |
|
("COMPUTER", 0.6, "tier_5_common") |
|
] |
|
|
|
filtered = self.generator._filter_by_difficulty(mock_results, difficulty) |
|
self.assertIsInstance(filtered, list) |
|
|
|
|
|
self.assertLessEqual(len(filtered), len(mock_results)) |
|
|
|
def test_multiple_topics(self): |
|
"""Test generation for multiple topics.""" |
|
self.generator.initialize() |
|
|
|
if not self.generator.is_initialized: |
|
self.skipTest("Generator initialization failed") |
|
|
|
topics = ["animals", "technology"] |
|
results = self.generator.generate_by_multiple_topics( |
|
topics=topics, |
|
words_per_topic=2, |
|
difficulty="medium" |
|
) |
|
|
|
self.assertIsInstance(results, dict) |
|
self.assertEqual(len(results), len(topics)) |
|
|
|
for topic in topics: |
|
self.assertIn(topic, results) |
|
self.assertIsInstance(results[topic], list) |
|
|
|
def test_stats_tracking(self): |
|
"""Test performance statistics tracking.""" |
|
self.generator.initialize() |
|
|
|
|
|
initial_stats = self.generator.get_stats() |
|
self.assertIsInstance(initial_stats, dict) |
|
self.assertIn('words_discovered', initial_stats) |
|
self.assertIn('clues_generated', initial_stats) |
|
|
|
|
|
if self.generator.thematic_ready or self.generator.api_ready: |
|
try: |
|
self.generator.generate_crossword_entries("test", 1, "medium") |
|
updated_stats = self.generator.get_stats() |
|
|
|
|
|
self.assertGreaterEqual(updated_stats['words_discovered'], initial_stats['words_discovered']) |
|
self.assertGreaterEqual(updated_stats['clues_generated'], initial_stats['clues_generated']) |
|
except Exception: |
|
pass |
|
|
|
def test_fallback_behavior(self): |
|
"""Test fallback behavior when components fail.""" |
|
self.generator.initialize() |
|
|
|
|
|
entries = self.generator.generate_crossword_entries( |
|
topic="nonexistent_impossible_topic_xyz123", |
|
num_words=1, |
|
difficulty="medium" |
|
) |
|
|
|
|
|
self.assertIsInstance(entries, list) |
|
|
|
def test_crossword_entry_structure(self): |
|
"""Test CrosswordEntry dataclass structure.""" |
|
|
|
entry = CrosswordEntry( |
|
word="TEST", |
|
clue="Sample clue", |
|
topic="testing", |
|
similarity_score=0.75, |
|
frequency_tier="tier_5_common", |
|
tier_description="Common words", |
|
clue_quality="GOOD", |
|
clue_model="test_model" |
|
) |
|
|
|
|
|
self.assertEqual(entry.word, "TEST") |
|
self.assertEqual(entry.clue, "Sample clue") |
|
self.assertEqual(entry.topic, "testing") |
|
self.assertEqual(entry.similarity_score, 0.75) |
|
self.assertEqual(entry.frequency_tier, "tier_5_common") |
|
self.assertEqual(entry.tier_description, "Common words") |
|
self.assertEqual(entry.clue_quality, "GOOD") |
|
self.assertEqual(entry.clue_model, "test_model") |
|
|
|
|
|
class TestIntegrationScenarios(unittest.TestCase): |
|
"""Test realistic integration scenarios.""" |
|
|
|
@classmethod |
|
def setUpClass(cls): |
|
"""Set up test environment.""" |
|
if not INTEGRATED_AVAILABLE: |
|
cls.skipTest(cls, "Integrated generator not available") |
|
|
|
cls.test_token = os.getenv('HF_TOKEN') |
|
|
|
def test_education_crossword_scenario(self): |
|
"""Test generating educational crossword content.""" |
|
|
|
cache_dir = str(Path(__file__).parent / 'model_cache') |
|
generator = IntegratedCrosswordGenerator( |
|
vocab_size_limit=50000, |
|
cache_dir=cache_dir |
|
) |
|
generator.initialize() |
|
|
|
if not generator.is_initialized: |
|
self.skipTest("Generator initialization failed") |
|
|
|
|
|
topics = ["science", "history", "mathematics"] |
|
|
|
for topic in topics: |
|
with self.subTest(topic=topic): |
|
entries = generator.generate_crossword_entries( |
|
topic=topic, |
|
num_words=3, |
|
difficulty="medium" |
|
) |
|
|
|
|
|
self.assertIsInstance(entries, list) |
|
for entry in entries: |
|
self.assertEqual(entry.topic, topic) |
|
|
|
self.assertGreaterEqual(len(entry.word), 3) |
|
|
|
def test_themed_puzzle_scenario(self): |
|
"""Test generating themed puzzle content.""" |
|
|
|
cache_dir = str(Path(__file__).parent / 'model_cache') |
|
generator = IntegratedCrosswordGenerator( |
|
vocab_size_limit=50000, |
|
cache_dir=cache_dir |
|
) |
|
generator.initialize() |
|
|
|
if not generator.is_initialized: |
|
self.skipTest("Generator initialization failed") |
|
|
|
|
|
theme = "ocean life" |
|
entries = generator.generate_crossword_entries( |
|
topic=theme, |
|
num_words=5, |
|
difficulty="medium" |
|
) |
|
|
|
if entries: |
|
|
|
for entry in entries: |
|
self.assertEqual(entry.topic, theme) |
|
self.assertIsInstance(entry.similarity_score, float) |
|
self.assertGreater(entry.similarity_score, 0.0) |
|
|
|
def test_performance_benchmarking(self): |
|
"""Test performance characteristics.""" |
|
|
|
cache_dir = str(Path(__file__).parent / 'model_cache') |
|
generator = IntegratedCrosswordGenerator( |
|
vocab_size_limit=50000, |
|
cache_dir=cache_dir |
|
) |
|
generator.initialize() |
|
|
|
if not generator.is_initialized: |
|
self.skipTest("Generator initialization failed") |
|
|
|
|
|
start_time = time.time() |
|
|
|
try: |
|
entries = generator.generate_crossword_entries( |
|
topic="technology", |
|
num_words=5, |
|
difficulty="medium" |
|
) |
|
|
|
generation_time = time.time() - start_time |
|
|
|
|
|
self.assertLess(generation_time, 60.0) |
|
|
|
if entries: |
|
avg_time_per_entry = generation_time / len(entries) |
|
self.assertLess(avg_time_per_entry, 20.0) |
|
|
|
except Exception as e: |
|
|
|
print(f"Performance test encountered: {e}") |
|
|
|
|
|
def run_comprehensive_tests(): |
|
"""Run all integration tests with detailed reporting.""" |
|
print("🧪 Comprehensive Integration Tests") |
|
print("=" * 60) |
|
print("📂 Using cached 50K vocabulary and embeddings from model_cache/") |
|
print("⚡ This significantly speeds up testing by avoiding re-computation") |
|
|
|
|
|
hf_token = os.getenv('HF_TOKEN') |
|
if not hf_token: |
|
print("⚠️ HF_TOKEN not set - API tests may be limited") |
|
|
|
if not INTEGRATED_AVAILABLE: |
|
print("❌ Integrated system not available - cannot run tests") |
|
return |
|
|
|
|
|
loader = unittest.TestLoader() |
|
suite = unittest.TestSuite() |
|
|
|
|
|
suite.addTests(loader.loadTestsFromTestCase(TestIntegratedCrosswordGenerator)) |
|
suite.addTests(loader.loadTestsFromTestCase(TestIntegrationScenarios)) |
|
|
|
|
|
runner = unittest.TextTestRunner(verbosity=2, stream=sys.stdout) |
|
result = runner.run(suite) |
|
|
|
|
|
print("\n" + "=" * 60) |
|
print("📊 TEST SUMMARY") |
|
print("=" * 60) |
|
print(f"Tests run: {result.testsRun}") |
|
print(f"Failures: {len(result.failures)}") |
|
print(f"Errors: {len(result.errors)}") |
|
print(f"Skipped: {len(result.skipped)}") |
|
|
|
if result.failures: |
|
print("\n❌ FAILURES:") |
|
for test, trace in result.failures: |
|
print(f" - {test}: {trace.splitlines()[-1]}") |
|
|
|
if result.errors: |
|
print("\n❌ ERRORS:") |
|
for test, trace in result.errors: |
|
print(f" - {test}: {trace.splitlines()[-1]}") |
|
|
|
if result.skipped: |
|
print("\n⏭️ SKIPPED:") |
|
for test, reason in result.skipped: |
|
print(f" - {test}: {reason}") |
|
|
|
success_rate = ((result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100) if result.testsRun > 0 else 0 |
|
print(f"\n✅ Success rate: {success_rate:.1f}%") |
|
|
|
if result.wasSuccessful(): |
|
print("🎉 All tests passed! Integration system is working correctly.") |
|
else: |
|
print("⚠️ Some tests failed. Check the system configuration.") |
|
|
|
return result.wasSuccessful() |
|
|
|
|
|
def main(): |
|
"""Run the comprehensive test suite.""" |
|
success = run_comprehensive_tests() |
|
sys.exit(0 if success else 1) |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |