diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50718f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,167 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Project-specific ignores +# Workspace directory with generated files +workspace/ +*.pyc + +# Configuration files with sensitive data +config.yaml +*.key +*.pem +*.p12 +*.pfx + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +*.log +logs/ + +# Temporary files +*.tmp +*.temp +temp/ +tmp/ diff --git a/README.md b/README.md index 1fac65c..3265096 100644 --- a/README.md +++ b/README.md @@ -33,25 +33,48 @@ This approach ensures correctness through differential testing while achieving o - Python 3.8 or higher - pip package manager -### Step 1: Clone the Repository +### Quick Setup (Recommended) ```bash -git clone -cd temp-agents +# 1. Clone the repository +git clone +cd Meta-HackerCup-AI-StarterKit + +# 2. Run the automated setup +python3 setup.py +``` + +The setup script will: +- Create `config.yaml` from template +- Create workspace directory +- Check for required files +- Provide next steps guidance + +### Manual Setup (Alternative) + +If you prefer manual setup: + +#### Step 1: Clone the Repository + +```bash +git clone https://github.com/amankumarkeshu/Meta-HackerCup-AI-StarterKit.git +cd Meta-HackerCup-AI-StarterKit ``` -### Step 2: Install Dependencies +#### Step 2: Install Dependencies ```bash pip install -r requirements.txt ``` This installs: -- `langchain` - Multi-agent framework -- `langchain-google-genai` - Google Gemini integration (FREE tier) -- `pyyaml` - Configuration management +- `langchain==0.3.0` - Multi-agent framework +- `langchain-google-genai==2.0.0` - Google Gemini integration (FREE tier) +- `pyyaml==6.0.2` - Configuration management +- `pytest==8.0.0` - Testing framework +- `pytest-cov==4.0.0` - Test coverage -### Step 3: Get FREE API Key +#### Step 3: Get FREE API Key **Google Gemini (FREE)** @@ -63,14 +86,16 @@ This installs: ### Set API Key -Edit `config.yaml`: +After running `python3 setup.py`, edit the generated `config.yaml`: ```yaml api_keys: # Google Gemini API Key - FREE TIER AVAILABLE! - google: "AIza...your-google-api-key" + google: "AIza...your-google-api-key" # Replace with your actual key ``` +**Security Note**: The setup script creates `config.yaml` from `config.template.yaml`. Never commit your actual API key to version control. + ### Choose Models The system uses **FREE** Google Gemini models. Default configuration: @@ -194,16 +219,57 @@ workspace/ ``` +## ๐Ÿงช Testing & Development + +### Run Tests + +The project includes a comprehensive test suite: + +```bash +# Run all tests +python run_tests.py + +# Or use pytest directly +pytest tests/ -v + +# Run tests with coverage +pytest tests/ --cov=. --cov-report=html +``` + +### Test Coverage + +- **CodeExecutor**: Tests for code execution, error handling, timeouts +- **OutputComparator**: Tests for file comparison and diff generation +- **ProblemSolverOrchestrator**: Tests for configuration and initialization +- **Integration Tests**: End-to-end workflow validation + +### Development Setup + +```bash +# Install development dependencies +pip install -r requirements.txt + +# Run setup script +python3 setup.py + +# Run tests to verify installation +python run_tests.py +``` + ## ๐Ÿ“ฆ Project Structure ``` -temp-agents/ +Meta-HackerCup-AI-StarterKit/ โ”œโ”€โ”€ PROBLEM.txt # Your problem statement (REQUIRED) -โ”œโ”€โ”€ config.yaml # Configuration file +โ”œโ”€โ”€ config.yaml # Configuration file (created by setup.py) +โ”œโ”€โ”€ config.template.yaml # Secure configuration template +โ”œโ”€โ”€ setup.py # Automated project setup script +โ”œโ”€โ”€ run_tests.py # Test runner script โ”œโ”€โ”€ main.py # Entry point โ”œโ”€โ”€ orchestrator.py # Multi-agent coordinator โ”œโ”€โ”€ viewer.html # Web-based results viewer -โ”œโ”€โ”€ requirements.txt # Python dependencies +โ”œโ”€โ”€ requirements.txt # Python dependencies (pinned versions) +โ”œโ”€โ”€ .gitignore # Git ignore rules (includes sensitive files) โ”œโ”€โ”€ README.md # This file โ”œโ”€โ”€ QUICKSTART.md # Quick reference guide โ”œโ”€โ”€ LICENSE # MIT License @@ -217,6 +283,11 @@ temp-agents/ โ”‚ โ”œโ”€โ”€ executor.py # Code execution utility โ”‚ โ”œโ”€โ”€ comparator.py # Output comparison utility โ”‚ โ””โ”€โ”€ progress.py # Live progress indicators +โ”œโ”€โ”€ tests/ # Unit test suite +โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ โ”œโ”€โ”€ test_executor.py # CodeExecutor tests +โ”‚ โ”œโ”€โ”€ test_comparator.py # OutputComparator tests +โ”‚ โ””โ”€โ”€ test_orchestrator.py # Orchestrator tests โ””โ”€โ”€ workspace/ # Generated files (gitignored) โ””โ”€โ”€ ... ``` diff --git a/config.template.yaml b/config.template.yaml new file mode 100644 index 0000000..f8b1fe1 --- /dev/null +++ b/config.template.yaml @@ -0,0 +1,40 @@ +# Multi-Agent Programming Task Solver Configuration Template +# Copy this file to config.yaml and fill in your API keys + +# API Keys Configuration +api_keys: + # Google Gemini API Key - FREE TIER AVAILABLE! + # Get yours at: https://aistudio.google.com/app/apikey + google: "your-google-api-key-here" + +# Model Configuration for Each Agent +# FORMAT: "google:model-name" +# +# Available Google Gemini Models (FREE TIER): +# It has a generous free tier for APIs, can start from here. (Details: https://ai.google.dev/gemini-api/docs/rate-limits) +# - "google:gemini-2.5-pro" # Most capable, slower (100 free requests / day) - winning model +# - "google:gemini-2.5-flash" # Latest & fastest (250 free requests / day) +# - "google:gemini-2.5-flash-lite" # Fast & Cheap (1000 free requests / day) - good for testing +# +models: + tester_agent: "google:gemini-2.5-flash-lite" + brute_agent: "google:gemini-2.5-flash-lite" + optimal_agent: "google:gemini-2.5-flash-lite" + +# Execution Parameters +execution: + max_optimal_attempts: 5 + timeout_seconds: 30 # Timeout for code execution + +# Output Configuration +output: + workspace_dir: "./workspace" + preserve_intermediate: true # Keep all generated files + +# File Names +files: + test_inputs: "small_inputs.txt" + brute_solution: "brute.py" + brute_outputs: "small_outputs.txt" + optimal_solution: "optimal.py" + optimal_outputs: "op.txt" diff --git a/requirements.txt b/requirements.txt index 48b5df1..5a6ac55 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -langchain>=0.3.0 -langchain-google-genai>=2.0.0 -pyyaml>=6.0 +langchain==0.3.0 +langchain-google-genai==2.0.0 +pyyaml==6.0.2 +pytest==8.0.0 +pytest-cov==4.0.0 diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 0000000..88f2d26 --- /dev/null +++ b/run_tests.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +""" +Test runner for Meta HackerCup AI StarterKit. +""" +import unittest +import sys +import os + +# Add the project root to the Python path +project_root = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, project_root) + + +def run_tests(): + """Run all unit tests.""" + # Discover and run tests + loader = unittest.TestLoader() + start_dir = os.path.join(project_root, 'tests') + suite = loader.discover(start_dir, pattern='test_*.py') + + # Run tests + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + + # Return exit code based on test results + return 0 if result.wasSuccessful() else 1 + + +if __name__ == '__main__': + sys.exit(run_tests()) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2935b3c --- /dev/null +++ b/setup.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +Setup script for Meta HackerCup AI StarterKit. +""" +import os +import shutil +import sys + + +def setup_project(): + """Set up the project for first-time use.""" + print("Setting up Meta HackerCup AI StarterKit...") + + # Check if config.yaml exists + if not os.path.exists('config.yaml'): + if os.path.exists('config.template.yaml'): + print("Creating config.yaml from template...") + shutil.copy('config.template.yaml', 'config.yaml') + print("โœ“ config.yaml created") + print("โš ๏ธ Please edit config.yaml and add your Google Gemini API key") + else: + print("โŒ config.template.yaml not found") + return False + else: + print("โœ“ config.yaml already exists") + + # Create workspace directory if it doesn't exist + if not os.path.exists('workspace'): + os.makedirs('workspace') + print("โœ“ workspace directory created") + else: + print("โœ“ workspace directory already exists") + + # Check if PROBLEM.txt exists + if not os.path.exists('PROBLEM.txt'): + print("โš ๏ธ PROBLEM.txt not found - you'll need to create this file with your problem statement") + else: + print("โœ“ PROBLEM.txt exists") + + print("\nSetup complete! Next steps:") + print("1. Edit config.yaml and add your Google Gemini API key") + print("2. Create or edit PROBLEM.txt with your problem statement") + print("3. Run: python main.py") + print("4. View results: python -m http.server 8000, then open http://localhost:8000/viewer.html") + + return True + + +if __name__ == '__main__': + success = setup_project() + sys.exit(0 if success else 1) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..1da5f0f --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Test package for Meta HackerCup AI StarterKit diff --git a/tests/test_comparator.py b/tests/test_comparator.py new file mode 100644 index 0000000..5d314b4 --- /dev/null +++ b/tests/test_comparator.py @@ -0,0 +1,116 @@ +""" +Unit tests for OutputComparator utility. +""" +import unittest +import tempfile +import os +from utils.comparator import OutputComparator + + +class TestOutputComparator(unittest.TestCase): + """Test cases for OutputComparator class.""" + + def setUp(self): + """Set up test fixtures.""" + self.temp_dir = tempfile.mkdtemp() + + def tearDown(self): + """Clean up test fixtures.""" + # Clean up temporary files + import shutil + shutil.rmtree(self.temp_dir, ignore_errors=True) + + def test_compare_identical_files(self): + """Test comparison of identical files.""" + content = "Hello World\n123\n" + + file1 = os.path.join(self.temp_dir, "file1.txt") + file2 = os.path.join(self.temp_dir, "file2.txt") + + with open(file1, 'w') as f: + f.write(content) + with open(file2, 'w') as f: + f.write(content) + + result = OutputComparator.compare(file1, file2) + self.assertTrue(result) + + def test_compare_different_files(self): + """Test comparison of different files.""" + file1 = os.path.join(self.temp_dir, "file1.txt") + file2 = os.path.join(self.temp_dir, "file2.txt") + + with open(file1, 'w') as f: + f.write("Hello World") + with open(file2, 'w') as f: + f.write("Goodbye World") + + result = OutputComparator.compare(file1, file2) + self.assertFalse(result) + + def test_compare_whitespace_differences(self): + """Test comparison ignoring leading/trailing whitespace.""" + file1 = os.path.join(self.temp_dir, "file1.txt") + file2 = os.path.join(self.temp_dir, "file2.txt") + + with open(file1, 'w') as f: + f.write(" Hello World \n") + with open(file2, 'w') as f: + f.write("Hello World") + + result = OutputComparator.compare(file1, file2) + self.assertTrue(result) + + def test_compare_nonexistent_file1(self): + """Test comparison with nonexistent first file.""" + file2 = os.path.join(self.temp_dir, "file2.txt") + with open(file2, 'w') as f: + f.write("content") + + result = OutputComparator.compare("nonexistent.txt", file2) + self.assertFalse(result) + + def test_compare_nonexistent_file2(self): + """Test comparison with nonexistent second file.""" + file1 = os.path.join(self.temp_dir, "file1.txt") + with open(file1, 'w') as f: + f.write("content") + + result = OutputComparator.compare(file1, "nonexistent.txt") + self.assertFalse(result) + + def test_get_diff_summary_identical(self): + """Test diff summary for identical files.""" + content = "Hello World" + + file1 = os.path.join(self.temp_dir, "file1.txt") + file2 = os.path.join(self.temp_dir, "file2.txt") + + with open(file1, 'w') as f: + f.write(content) + with open(file2, 'w') as f: + f.write(content) + + diff = OutputComparator.get_diff_summary(file1, file2) + self.assertEqual(diff, "Outputs match!") + + def test_get_diff_summary_different(self): + """Test diff summary for different files.""" + file1 = os.path.join(self.temp_dir, "file1.txt") + file2 = os.path.join(self.temp_dir, "file2.txt") + + with open(file1, 'w') as f: + f.write("Expected output") + with open(file2, 'w') as f: + f.write("Actual output") + + diff = OutputComparator.get_diff_summary(file1, file2) + self.assertIn("Outputs differ", diff) + self.assertIn("Expected:", diff) + self.assertIn("Actual:", diff) + self.assertIn("Expected output", diff) + self.assertIn("Actual output", diff) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_executor.py b/tests/test_executor.py new file mode 100644 index 0000000..2f5435e --- /dev/null +++ b/tests/test_executor.py @@ -0,0 +1,141 @@ +""" +Unit tests for CodeExecutor utility. +""" +import unittest +import tempfile +import os +from utils.executor import CodeExecutor + + +class TestCodeExecutor(unittest.TestCase): + """Test cases for CodeExecutor class.""" + + def setUp(self): + """Set up test fixtures.""" + self.executor = CodeExecutor(timeout=5) + self.temp_dir = tempfile.mkdtemp() + + def tearDown(self): + """Clean up test fixtures.""" + # Clean up temporary files + import shutil + shutil.rmtree(self.temp_dir, ignore_errors=True) + + def test_execute_simple_code(self): + """Test execution of simple Python code.""" + # Create a simple Python script + code_content = """ +print("Hello, World!") +""" + code_file = os.path.join(self.temp_dir, "test_code.py") + with open(code_file, 'w') as f: + f.write(code_content) + + # Create empty input file + input_file = os.path.join(self.temp_dir, "input.txt") + with open(input_file, 'w') as f: + f.write("") + + # Output file + output_file = os.path.join(self.temp_dir, "output.txt") + + # Execute + success, error = self.executor.execute(code_file, input_file, output_file) + + # Verify + self.assertTrue(success, f"Execution failed: {error}") + self.assertEqual(error, "") + + # Check output + with open(output_file, 'r') as f: + output = f.read().strip() + self.assertEqual(output, "Hello, World!") + + def test_execute_with_input(self): + """Test execution with input data.""" + # Create a Python script that reads input + code_content = """ +import sys +line = sys.stdin.readline().strip() +print(f"Input was: {line}") +""" + code_file = os.path.join(self.temp_dir, "test_input.py") + with open(code_file, 'w') as f: + f.write(code_content) + + # Create input file + input_file = os.path.join(self.temp_dir, "input.txt") + with open(input_file, 'w') as f: + f.write("test input\n") + + # Output file + output_file = os.path.join(self.temp_dir, "output.txt") + + # Execute + success, error = self.executor.execute(code_file, input_file, output_file) + + # Verify + self.assertTrue(success, f"Execution failed: {error}") + + # Check output + with open(output_file, 'r') as f: + output = f.read().strip() + self.assertEqual(output, "Input was: test input") + + def test_execute_nonexistent_code_file(self): + """Test execution with nonexistent code file.""" + input_file = os.path.join(self.temp_dir, "input.txt") + output_file = os.path.join(self.temp_dir, "output.txt") + + # Create input file + with open(input_file, 'w') as f: + f.write("") + + success, error = self.executor.execute("nonexistent.py", input_file, output_file) + + self.assertFalse(success) + self.assertIn("Code file not found", error) + + def test_execute_nonexistent_input_file(self): + """Test execution with nonexistent input file.""" + code_file = os.path.join(self.temp_dir, "test.py") + output_file = os.path.join(self.temp_dir, "output.txt") + + # Create code file + with open(code_file, 'w') as f: + f.write("print('test')") + + success, error = self.executor.execute(code_file, "nonexistent_input.txt", output_file) + + self.assertFalse(success) + self.assertIn("Input file not found", error) + + def test_execute_syntax_error(self): + """Test execution with syntax error in code.""" + # Create a Python script with syntax error + code_content = """ +print("Hello World" +# Missing closing parenthesis +""" + code_file = os.path.join(self.temp_dir, "syntax_error.py") + with open(code_file, 'w') as f: + f.write(code_content) + + # Create input file + input_file = os.path.join(self.temp_dir, "input.txt") + with open(input_file, 'w') as f: + f.write("") + + # Output file + output_file = os.path.join(self.temp_dir, "output.txt") + + # Execute + success, error = self.executor.execute(code_file, input_file, output_file) + + # Verify failure + self.assertFalse(success) + self.assertIn("Execution failed", error) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py new file mode 100644 index 0000000..ac869a1 --- /dev/null +++ b/tests/test_orchestrator.py @@ -0,0 +1,141 @@ +""" +Unit tests for ProblemSolverOrchestrator. +""" +import unittest +import tempfile +import os +import yaml +from unittest.mock import patch, MagicMock +from orchestrator import ProblemSolverOrchestrator + + +class TestProblemSolverOrchestrator(unittest.TestCase): + """Test cases for ProblemSolverOrchestrator class.""" + + def setUp(self): + """Set up test fixtures.""" + self.temp_dir = tempfile.mkdtemp() + + # Create a test config file + self.config_data = { + 'api_keys': { + 'google': 'test-api-key' + }, + 'models': { + 'tester_agent': 'google:gemini-2.5-flash-lite', + 'brute_agent': 'google:gemini-2.5-flash-lite', + 'optimal_agent': 'google:gemini-2.5-flash-lite' + }, + 'execution': { + 'max_optimal_attempts': 3, + 'timeout_seconds': 10 + }, + 'output': { + 'workspace_dir': os.path.join(self.temp_dir, 'workspace') + }, + 'files': { + 'test_inputs': 'small_inputs.txt', + 'brute_solution': 'brute.py', + 'brute_outputs': 'small_outputs.txt', + 'optimal_solution': 'optimal.py', + 'optimal_outputs': 'op.txt' + } + } + + self.config_file = os.path.join(self.temp_dir, 'test_config.yaml') + with open(self.config_file, 'w') as f: + yaml.dump(self.config_data, f) + + def tearDown(self): + """Clean up test fixtures.""" + import shutil + shutil.rmtree(self.temp_dir, ignore_errors=True) + + @patch.dict(os.environ, {}, clear=True) + def test_init_sets_environment_variable(self): + """Test that initialization sets the Google API key environment variable.""" + with patch('orchestrator.TesterAgent'), \ + patch('orchestrator.BruteAgent'), \ + patch('orchestrator.OptimalAgent'): + + orchestrator = ProblemSolverOrchestrator(self.config_file) + + self.assertEqual(os.environ.get('GOOGLE_API_KEY'), 'test-api-key') + + @patch.dict(os.environ, {}, clear=True) + def test_init_skips_placeholder_api_key(self): + """Test that placeholder API keys are not set as environment variables.""" + # Modify config to use placeholder + self.config_data['api_keys']['google'] = 'your-google-api-key-here' + with open(self.config_file, 'w') as f: + yaml.dump(self.config_data, f) + + with patch('orchestrator.TesterAgent'), \ + patch('orchestrator.BruteAgent'), \ + patch('orchestrator.OptimalAgent'): + + orchestrator = ProblemSolverOrchestrator(self.config_file) + + self.assertIsNone(os.environ.get('GOOGLE_API_KEY')) + + def test_init_creates_workspace_directory(self): + """Test that initialization creates the workspace directory.""" + with patch('orchestrator.TesterAgent'), \ + patch('orchestrator.BruteAgent'), \ + patch('orchestrator.OptimalAgent'): + + orchestrator = ProblemSolverOrchestrator(self.config_file) + + self.assertTrue(os.path.exists(orchestrator.workspace)) + + def test_init_sets_file_paths(self): + """Test that initialization sets correct file paths.""" + with patch('orchestrator.TesterAgent'), \ + patch('orchestrator.BruteAgent'), \ + patch('orchestrator.OptimalAgent'): + + orchestrator = ProblemSolverOrchestrator(self.config_file) + + expected_workspace = os.path.join(self.temp_dir, 'workspace') + self.assertEqual(orchestrator.workspace, expected_workspace) + + # Check file paths + expected_files = { + 'test_inputs': os.path.join(expected_workspace, 'small_inputs.txt'), + 'brute_solution': os.path.join(expected_workspace, 'brute.py'), + 'brute_outputs': os.path.join(expected_workspace, 'small_outputs.txt'), + 'optimal_solution': os.path.join(expected_workspace, 'optimal.py'), + 'optimal_outputs': os.path.join(expected_workspace, 'op.txt') + } + + self.assertEqual(orchestrator.files, expected_files) + + def test_init_sets_max_attempts(self): + """Test that initialization sets max attempts from config.""" + with patch('orchestrator.TesterAgent'), \ + patch('orchestrator.BruteAgent'), \ + patch('orchestrator.OptimalAgent'): + + orchestrator = ProblemSolverOrchestrator(self.config_file) + + self.assertEqual(orchestrator.max_attempts, 3) + + def test_config_file_not_found(self): + """Test behavior when config file is not found.""" + with self.assertRaises(FileNotFoundError): + ProblemSolverOrchestrator('nonexistent_config.yaml') + + def test_invalid_config_structure(self): + """Test behavior with invalid config structure.""" + # Create config with missing required keys + invalid_config = {'invalid': 'config'} + invalid_config_file = os.path.join(self.temp_dir, 'invalid_config.yaml') + with open(invalid_config_file, 'w') as f: + yaml.dump(invalid_config, f) + + with self.assertRaises(KeyError): + ProblemSolverOrchestrator(invalid_config_file) + + +if __name__ == '__main__': + unittest.main()