# -*- coding: utf-8 -*- """Glob tool test case.""" import os import tempfile from unittest.async_case import IsolatedAsyncioTestCase from utils import AnyString from agentscope.tool import Glob from agentscope.permission import ( PermissionContext, PermissionBehavior, PermissionRule, ) class GlobToolTest(IsolatedAsyncioTestCase): """The glob tool test case.""" async def asyncSetUp(self) -> None: """The async setup method.""" self.glob_tool = Glob() # Create a temporary directory with test files self.temp_dir = tempfile.mkdtemp() # Create test files with open( os.path.join(self.temp_dir, "test1.py"), "w", encoding="utf-8", ): pass with open( os.path.join(self.temp_dir, "test2.py"), "w", encoding="utf-8", ): pass with open( os.path.join(self.temp_dir, "test.txt"), "w", encoding="utf-8", ): pass # Create subdirectory sub_dir = os.path.join(self.temp_dir, "subdir") os.makedirs(sub_dir) with open(os.path.join(sub_dir, "test3.py"), "w", encoding="utf-8"): pass async def asyncTearDown(self) -> None: """Clean up temporary files.""" import shutil if os.path.exists(self.temp_dir): shutil.rmtree(self.temp_dir) async def test_tool_properties(self) -> None: """Test glob tool properties.""" self.assertEqual(self.glob_tool.name, "Glob") self.assertIsInstance(self.glob_tool.description, str) self.assertIsInstance(self.glob_tool.input_schema, dict) self.assertFalse(self.glob_tool.is_mcp) self.assertTrue(self.glob_tool.is_read_only) self.assertTrue(self.glob_tool.is_concurrency_safe) async def test_check_permissions(self) -> None: """Test glob tool permission checking.""" context = PermissionContext() tool_input = {"pattern": "*.py"} decision = await self.glob_tool.check_permissions(tool_input, context) # Read/Glob/Grep are read-only, return PASSTHROUGH self.assertEqual(decision.behavior, PermissionBehavior.PASSTHROUGH) async def test_simple_pattern(self) -> None: """Test simple glob pattern.""" chunk = await self.glob_tool( pattern="*.py", path=self.temp_dir, ) self.assertEqual(chunk.state, "running") # Should find test1.py and test2.py content = chunk.content[0].text self.assertIn("test1.py", content) self.assertIn("test2.py", content) self.assertNotIn("test.txt", content) async def test_recursive_pattern(self) -> None: """Test recursive glob pattern.""" chunk = await self.glob_tool( pattern="**/*.py", path=self.temp_dir, ) content = chunk.content[0].text # Should find all .py files including in subdirectory self.assertIn("test1.py", content) self.assertIn("test2.py", content) self.assertIn("test3.py", content) async def test_windows_style_separator_pattern(self) -> None: """Test glob patterns that use backslashes as path separators.""" chunk = await self.glob_tool( pattern=r"subdir\*.py", path=self.temp_dir, ) self.assertDictEqual( chunk.model_dump(), { "content": [ { "type": "text", "text": os.path.join( self.temp_dir, "subdir", "test3.py", ), "id": AnyString(), }, ], "state": "running", "is_last": True, "metadata": {}, "id": AnyString(), }, ) async def test_no_matches(self) -> None: """Test pattern with no matches.""" chunk = await self.glob_tool( pattern="*.nonexistent", path=self.temp_dir, ) self.assertEqual(chunk.state, "running") self.assertIn("No files found", chunk.content[0].text) async def test_match_rule_path(self) -> None: """Test match_rule with path patterns.""" # Test matching explicit path self.assertTrue( await self.glob_tool.match_rule( self.temp_dir, {"path": self.temp_dir, "pattern": "*.py"}, ), ) # Test wildcard pattern matching path parent_dir = os.path.dirname(self.temp_dir) self.assertTrue( await self.glob_tool.match_rule( parent_dir + "/**", {"path": self.temp_dir, "pattern": "*.py"}, ), ) # Test non-matching path self.assertFalse( await self.glob_tool.match_rule( "/some/other/path/**", {"path": self.temp_dir, "pattern": "*.py"}, ), ) async def test_match_rule_pattern(self) -> None: """Test match_rule with pattern matching.""" # Test matching against the pattern itself self.assertTrue( await self.glob_tool.match_rule( "*.py", {"pattern": "*.py"}, ), ) # Test wildcard pattern matching self.assertTrue( await self.glob_tool.match_rule( "**/*.py", {"pattern": "src/**/*.py"}, ), ) # Test non-matching pattern self.assertFalse( await self.glob_tool.match_rule( "*.txt", {"pattern": "*.py"}, ), ) async def test_match_rule_path_priority(self) -> None: """Test that path matching takes priority over pattern matching.""" # If path matches, should return True even if pattern doesn't self.assertTrue( await self.glob_tool.match_rule( self.temp_dir, {"path": self.temp_dir, "pattern": "*.txt"}, ), ) async def test_generate_suggestions_with_path(self) -> None: """Test generate_suggestions for glob with explicit path.""" suggestions = await self.glob_tool.generate_suggestions( {"path": self.temp_dir, "pattern": "*.py"}, ) self.assertIsInstance(suggestions, list) self.assertGreater(len(suggestions), 0) self.assertIsInstance(suggestions[0], PermissionRule) # Should suggest directory pattern abs_path = os.path.abspath(self.temp_dir) expected_pattern = abs_path.rstrip("/") + "/**" suggestion_contents = [s.rule_content for s in suggestions] self.assertIn(expected_pattern, suggestion_contents) async def test_generate_suggestions_defaults_to_cwd(self) -> None: """Test generate_suggestions defaults to cwd when no path provided.""" suggestions = await self.glob_tool.generate_suggestions( {"pattern": "*.py"}, ) self.assertIsInstance(suggestions, list) self.assertGreater(len(suggestions), 0) cwd = os.getcwd() expected_pattern = os.path.abspath(cwd).rstrip("/") + "/**" suggestion_contents = [s.rule_content for s in suggestions] self.assertIn(expected_pattern, suggestion_contents)