import os import shutil import tempfile from pathlib import Path import pytest from crewai.cli import utils @pytest.fixture def temp_tree(): root_dir = tempfile.mkdtemp() create_file(os.path.join(root_dir, "file1.txt"), "Hello, world!") create_file(os.path.join(root_dir, "file2.txt"), "Another file") os.mkdir(os.path.join(root_dir, "empty_dir")) nested_dir = os.path.join(root_dir, "nested_dir") os.mkdir(nested_dir) create_file(os.path.join(nested_dir, "nested_file.txt"), "Nested content") yield root_dir shutil.rmtree(root_dir) def create_file(path, content): with open(path, "w") as f: f.write(content) def test_tree_find_and_replace_file_content(temp_tree): utils.tree_find_and_replace(temp_tree, "world", "universe") with open(os.path.join(temp_tree, "file1.txt"), "r") as f: assert f.read() == "Hello, universe!" def test_tree_find_and_replace_file_name(temp_tree): old_path = os.path.join(temp_tree, "file2.txt") new_path = os.path.join(temp_tree, "file2_renamed.txt") os.rename(old_path, new_path) utils.tree_find_and_replace(temp_tree, "renamed", "modified") assert os.path.exists(os.path.join(temp_tree, "file2_modified.txt")) assert not os.path.exists(new_path) def test_tree_find_and_replace_directory_name(temp_tree): utils.tree_find_and_replace(temp_tree, "empty", "renamed") assert os.path.exists(os.path.join(temp_tree, "renamed_dir")) assert not os.path.exists(os.path.join(temp_tree, "empty_dir")) def test_tree_find_and_replace_nested_content(temp_tree): utils.tree_find_and_replace(temp_tree, "Nested", "Updated") with open(os.path.join(temp_tree, "nested_dir", "nested_file.txt"), "r") as f: assert f.read() == "Updated content" def test_tree_find_and_replace_no_matches(temp_tree): utils.tree_find_and_replace(temp_tree, "nonexistent", "replacement") assert set(os.listdir(temp_tree)) == { "file1.txt", "file2.txt", "empty_dir", "nested_dir", } def test_tree_copy_full_structure(temp_tree): dest_dir = tempfile.mkdtemp() try: utils.tree_copy(temp_tree, dest_dir) assert set(os.listdir(dest_dir)) == set(os.listdir(temp_tree)) assert os.path.isfile(os.path.join(dest_dir, "file1.txt")) assert os.path.isfile(os.path.join(dest_dir, "file2.txt")) assert os.path.isdir(os.path.join(dest_dir, "empty_dir")) assert os.path.isdir(os.path.join(dest_dir, "nested_dir")) assert os.path.isfile(os.path.join(dest_dir, "nested_dir", "nested_file.txt")) finally: shutil.rmtree(dest_dir) def test_tree_copy_preserve_content(temp_tree): dest_dir = tempfile.mkdtemp() try: utils.tree_copy(temp_tree, dest_dir) with open(os.path.join(dest_dir, "file1.txt"), "r") as f: assert f.read() == "Hello, world!" with open(os.path.join(dest_dir, "nested_dir", "nested_file.txt"), "r") as f: assert f.read() == "Nested content" finally: shutil.rmtree(dest_dir) def test_tree_copy_to_existing_directory(temp_tree): dest_dir = tempfile.mkdtemp() try: create_file(os.path.join(dest_dir, "existing_file.txt"), "I was here first") utils.tree_copy(temp_tree, dest_dir) assert os.path.isfile(os.path.join(dest_dir, "existing_file.txt")) assert os.path.isfile(os.path.join(dest_dir, "file1.txt")) finally: shutil.rmtree(dest_dir) @pytest.fixture def temp_project_dir(): """Create a temporary directory for testing tool extraction.""" with tempfile.TemporaryDirectory() as temp_dir: yield Path(temp_dir) def create_init_file(directory, content): return create_file(directory / "__init__.py", content) def test_extract_available_exports_empty_project(temp_project_dir, capsys): with pytest.raises(SystemExit): utils.extract_available_exports(dir_path=temp_project_dir) captured = capsys.readouterr() assert "No valid tools were exposed in your __init__.py file" in captured.out def test_extract_available_exports_no_init_file(temp_project_dir, capsys): (temp_project_dir / "some_file.py").write_text("print('hello')") with pytest.raises(SystemExit): utils.extract_available_exports(dir_path=temp_project_dir) captured = capsys.readouterr() assert "No valid tools were exposed in your __init__.py file" in captured.out def test_extract_available_exports_empty_init_file(temp_project_dir, capsys): create_init_file(temp_project_dir, "") with pytest.raises(SystemExit): utils.extract_available_exports(dir_path=temp_project_dir) captured = capsys.readouterr() assert "Warning: No __all__ defined in" in captured.out def test_extract_available_exports_no_all_variable(temp_project_dir, capsys): create_init_file( temp_project_dir, "from crewai.tools import BaseTool\n\nclass MyTool(BaseTool):\n pass", ) with pytest.raises(SystemExit): utils.extract_available_exports(dir_path=temp_project_dir) captured = capsys.readouterr() assert "Warning: No __all__ defined in" in captured.out def test_extract_available_exports_valid_base_tool_class(temp_project_dir): create_init_file( temp_project_dir, """from crewai.tools import BaseTool class MyTool(BaseTool): name: str = "my_tool" description: str = "A test tool" __all__ = ['MyTool'] """, ) tools = utils.extract_available_exports(dir_path=temp_project_dir) assert [{"name": "MyTool"}] == tools def test_extract_available_exports_valid_tool_decorator(temp_project_dir): create_init_file( temp_project_dir, """from crewai.tools import tool @tool def my_tool_function(text: str) -> str: \"\"\"A test tool function\"\"\" return text __all__ = ['my_tool_function'] """, ) tools = utils.extract_available_exports(dir_path=temp_project_dir) assert [{"name": "my_tool_function"}] == tools def test_extract_available_exports_multiple_valid_tools(temp_project_dir): create_init_file( temp_project_dir, """from crewai.tools import BaseTool, tool class MyTool(BaseTool): name: str = "my_tool" description: str = "A test tool" @tool def my_tool_function(text: str) -> str: \"\"\"A test tool function\"\"\" return text __all__ = ['MyTool', 'my_tool_function'] """, ) tools = utils.extract_available_exports(dir_path=temp_project_dir) assert [{"name": "MyTool"}, {"name": "my_tool_function"}] == tools def test_extract_available_exports_with_invalid_tool_decorator(temp_project_dir): create_init_file( temp_project_dir, """from crewai.tools import BaseTool class MyTool(BaseTool): name: str = "my_tool" description: str = "A test tool" def not_a_tool(): pass __all__ = ['MyTool', 'not_a_tool'] """, ) tools = utils.extract_available_exports(dir_path=temp_project_dir) assert [{"name": "MyTool"}] == tools def test_extract_available_exports_import_error(temp_project_dir, capsys): create_init_file( temp_project_dir, """from nonexistent_module import something class MyTool(BaseTool): pass __all__ = ['MyTool'] """, ) with pytest.raises(SystemExit): utils.extract_available_exports(dir_path=temp_project_dir) captured = capsys.readouterr() assert "nonexistent_module" in captured.out def test_extract_available_exports_syntax_error(temp_project_dir, capsys): create_init_file( temp_project_dir, """from crewai.tools import BaseTool class MyTool(BaseTool): # Missing closing parenthesis def __init__(self, name: pass __all__ = ['MyTool'] """, ) with pytest.raises(SystemExit): utils.extract_available_exports(dir_path=temp_project_dir) captured = capsys.readouterr() assert "was never closed" in captured.out @pytest.fixture def mock_crew(): from crewai.crew import Crew class MockCrew(Crew): def __init__(self): pass return MockCrew() @pytest.fixture def temp_crew_project(): with tempfile.TemporaryDirectory() as temp_dir: old_cwd = os.getcwd() os.chdir(temp_dir) crew_content = """ from crewai.crew import Crew from crewai.agent import Agent def create_crew() -> Crew: agent = Agent(role="test", goal="test", backstory="test") return Crew(agents=[agent], tasks=[]) # Direct crew instance direct_crew = Crew(agents=[], tasks=[]) """ with open("crew.py", "w") as f: f.write(crew_content) os.makedirs("src", exist_ok=True) with open(os.path.join("src", "crew.py"), "w") as f: f.write(crew_content) # Create a src/templates directory that should be ignored os.makedirs(os.path.join("src", "templates"), exist_ok=True) with open(os.path.join("src", "templates", "crew.py"), "w") as f: f.write("# This should be ignored") yield temp_dir os.chdir(old_cwd) def test_get_crews_finds_valid_crews(temp_crew_project, monkeypatch, mock_crew): def mock_fetch_crews(module_attr): return [mock_crew] monkeypatch.setattr(utils, "fetch_crews", mock_fetch_crews) crews = utils.get_crews() assert len(crews) > 0 assert mock_crew in crews def test_get_crews_with_nonexistent_file(temp_crew_project): crews = utils.get_crews(crew_path="nonexistent.py", require=False) assert len(crews) == 0 def test_get_crews_with_required_nonexistent_file(temp_crew_project, capsys): with pytest.raises(SystemExit): utils.get_crews(crew_path="nonexistent.py", require=True) captured = capsys.readouterr() assert "No valid Crew instance found" in captured.out def test_get_crews_with_invalid_module(temp_crew_project, capsys): with open("crew.py", "w") as f: f.write("import nonexistent_module\n") crews = utils.get_crews(crew_path="crew.py", require=False) assert len(crews) == 0 with pytest.raises(SystemExit): utils.get_crews(crew_path="crew.py", require=True) captured = capsys.readouterr() assert "Error" in captured.out def test_get_crews_ignores_template_directories( temp_crew_project, monkeypatch, mock_crew ): template_crew_detected = False def mock_fetch_crews(module_attr): nonlocal template_crew_detected if hasattr(module_attr, "__file__") and "templates" in module_attr.__file__: template_crew_detected = True return [mock_crew] monkeypatch.setattr(utils, "fetch_crews", mock_fetch_crews) utils.get_crews() assert not template_crew_detected