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