11import os
22import re
33import tempfile
4- from pathlib import Path
4+
55import pytest
66
7- from serena .tools .file_tools import ReplaceRegexTool
87from serena .tools import SUCCESS_RESULT
8+ from serena .tools .file_tools import ReplaceRegexTool
99
1010
1111class MockProject :
@@ -18,15 +18,16 @@ def validate_relative_path(self, path):
1818 return True
1919
2020 def read_file (self , path ):
21- with open (os .path .join (self .root_dir , path ), 'r' ) as f :
21+ with open (os .path .join (self .root_dir , path )) as f :
2222 return f .read ()
2323
2424 def is_ignored_path (self , path ):
2525 return False
26-
26+
27+
2728class MockProjectConfig :
2829 def __init__ (self ):
29- self .encoding = ' utf-8'
30+ self .encoding = " utf-8"
3031
3132
3233class MockAgent :
@@ -35,21 +36,22 @@ def __init__(self, project):
3536 self ._active_project = project
3637 self .lines_read = None
3738 self .serena_config = None
38-
39+
3940 def get_active_project_or_raise (self ):
4041 return self .project
41-
42+
4243 def get_active_project (self ):
4344 return self .project
44-
45+
4546 def is_using_language_server (self ):
4647 return False
47-
48+
4849 def record_tool_usage_if_enabled (self , * args , ** kwargs ):
4950 pass
50-
51+
5152 def issue_task (self , task , name ):
5253 from concurrent .futures import Future
54+
5355 future = Future ()
5456 future .set_result (task ())
5557 return future
@@ -69,73 +71,73 @@ def setup(self):
6971 # Setup mock project and agent
7072 project = MockProject (temp_dir )
7173 agent = MockAgent (project )
72-
74+
7375 # Create the tool
7476 tool = ReplaceRegexTool (agent )
75-
77+
7678 yield temp_dir , "test_file.py" , tool
7779
7880 def test_replace_with_newline (self , setup ):
7981 """Test replacing text with a string containing newline escape sequences."""
8082 temp_dir , test_file_path , tool = setup
81-
83+
8284 # Replace text with a string containing \n
8385 result = tool .apply (
8486 relative_path = test_file_path ,
8587 regex = r"print\(f'Some text'\)" ,
8688 repl = r"print(f'\n Confidence Calculation Breakdown:')" ,
8789 )
88-
90+
8991 assert result == SUCCESS_RESULT
90-
92+
9193 # Read the file and check the content
92- with open (os .path .join (temp_dir , test_file_path ), "r" ) as f :
94+ with open (os .path .join (temp_dir , test_file_path )) as f :
9395 content = f .read ()
94-
96+
9597 # The newline should be preserved as \n, not converted to a literal newline
9698 assert "print(f'\\ n Confidence Calculation Breakdown:')" in content
9799 assert "print(f'\n Confidence" not in content
98-
100+
99101 def test_replace_with_tab (self , setup ):
100102 """Test replacing text with a string containing tab escape sequences."""
101103 temp_dir , test_file_path , tool = setup
102-
104+
103105 # Replace text with a string containing \t
104106 result = tool .apply (
105107 relative_path = test_file_path ,
106108 regex = r"print\(f'Some text'\)" ,
107109 repl = r"print(f'\t Indented text')" ,
108110 )
109-
111+
110112 assert result == SUCCESS_RESULT
111-
113+
112114 # Read the file and check the content
113- with open (os .path .join (temp_dir , test_file_path ), "r" ) as f :
115+ with open (os .path .join (temp_dir , test_file_path )) as f :
114116 content = f .read ()
115-
117+
116118 # The tab should be preserved as \t, not converted to a literal tab
117119 assert "print(f'\\ t Indented text')" in content
118-
120+
119121 def test_replace_with_multiple_escapes (self , setup ):
120122 """Test replacing text with a string containing multiple escape sequences."""
121123 temp_dir , test_file_path , tool = setup
122-
124+
123125 # Replace text with a string containing multiple escape sequences
124126 result = tool .apply (
125127 relative_path = test_file_path ,
126128 regex = r"print\(f'Some text'\)" ,
127129 repl = r"print(f'\n\t\r Multiple escapes')" ,
128130 )
129-
131+
130132 assert result == SUCCESS_RESULT
131-
133+
132134 # Read the file and check the content
133- with open (os .path .join (temp_dir , test_file_path ), "r" ) as f :
135+ with open (os .path .join (temp_dir , test_file_path )) as f :
134136 content = f .read ()
135-
137+
136138 # All escape sequences should be preserved
137139 assert "print(f'\\ n\\ t\\ r Multiple escapes')" in content
138-
140+
139141 def test_replace_with_backreferences (self , setup ):
140142 """Test replacing text with backreferences."""
141143 temp_dir , test_file_path , tool = setup
@@ -156,94 +158,94 @@ def test_replace_with_backreferences(self, setup):
156158 assert result == SUCCESS_RESULT
157159
158160 # Read the file and check the content
159- with open (backrefs_file , "r" ) as f :
161+ with open (backrefs_file ) as f :
160162 content = f .read ()
161163
162164 # Backreferences should work and newlines should be preserved
163165 assert "def function_name(arg1, arg2):" in content
164166 assert " # Function with arguments: arg1, arg2" in content
165167 assert " # Returns the sum" in content
166-
168+
167169 def test_simulated_api_call (self , setup ):
168170 """Test that simulates how the tool is called through the API."""
169171 temp_dir , test_file_path , tool = setup
170-
172+
171173 # Create a test file with an f-string
172174 fstring_file = os .path .join (temp_dir , "fstring.py" )
173175 with open (fstring_file , "w" ) as f :
174176 f .write ("print(f'Some text')\n " )
175-
177+
176178 # This is how the replacement string might look when it comes from JSON
177179 # The \n is already escaped once in the JSON string
178180 json_style_repl = "print(f'\\ n Confidence Calculation Breakdown:')"
179-
181+
180182 # Apply the replacement
181183 result = tool .apply (
182184 relative_path = "fstring.py" ,
183185 regex = r"print\(f'Some text'\)" ,
184186 repl = json_style_repl ,
185187 )
186-
188+
187189 assert result == SUCCESS_RESULT
188-
190+
189191 # Read the file and check the content
190- with open (fstring_file , "r" ) as f :
192+ with open (fstring_file ) as f :
191193 content = f .read ()
192-
194+
193195 # The newline should be preserved as \n, not converted to a literal newline
194196 assert "print(f'\\ n Confidence Calculation Breakdown:')" in content
195197 # This is what we don't want to see - a literal newline in the string
196198 assert "print(f'\n Confidence" not in content
197-
199+
198200 def test_real_world_scenario (self , setup ):
199201 """Test that simulates the real-world scenario where the issue occurs."""
200202 temp_dir , test_file_path , tool = setup
201-
203+
202204 # Create a test file with an indented f-string (similar to the issue description)
203205 fstring_file = os .path .join (temp_dir , "real_world.py" )
204206 with open (fstring_file , "w" ) as f :
205207 f .write (" print(f'Some text')\n " )
206-
208+
207209 # This simulates how the replacement string might be processed in production
208210 # The issue description shows the newline being interpreted literally
209211 repl = "print(f'\n Confidence Calculation Breakdown:')"
210-
212+
211213 # Apply the replacement directly using re.sub to simulate what might be happening
212214 # in production without the double-escaping
213- with open (fstring_file , "r" ) as f :
215+ with open (fstring_file ) as f :
214216 content = f .read ()
215-
217+
216218 # This is what might be happening in production - direct substitution without proper escaping
217219 broken_content = re .sub (r"print\(f'Some text'\)" , repl , content , flags = re .DOTALL | re .MULTILINE )
218-
220+
219221 with open (fstring_file , "w" ) as f :
220222 f .write (broken_content )
221-
223+
222224 # Read the file and check if it contains the broken string (literal newline)
223- with open (fstring_file , "r" ) as f :
225+ with open (fstring_file ) as f :
224226 content = f .read ()
225-
227+
226228 # This should be true if the issue is reproduced - the string is broken across lines
227229 assert "print(f'\n Confidence" in content
228-
230+
229231 # Now fix the file and try with our tool
230232 with open (fstring_file , "w" ) as f :
231233 f .write (" print(f'Some text')\n " )
232-
234+
233235 # Apply the replacement using our tool
234236 result = tool .apply (
235237 relative_path = "real_world.py" ,
236238 regex = r"print\(f'Some text'\)" ,
237239 repl = r"print(f'\n Confidence Calculation Breakdown:')" ,
238240 )
239-
241+
240242 assert result == SUCCESS_RESULT
241-
243+
242244 # Read the file and check the content
243- with open (fstring_file , "r" ) as f :
245+ with open (fstring_file ) as f :
244246 content = f .read ()
245-
247+
246248 # The newline should be preserved as \n, not converted to a literal newline
247249 assert "print(f'\\ n Confidence Calculation Breakdown:')" in content
248250 # This is what we don't want to see - a literal newline in the string
249- assert "print(f'\n Confidence" not in content
251+ assert "print(f'\n Confidence" not in content
0 commit comments