Coverage for src/renaissance/recipes/taut_to_python_unittest.py: 84%

353 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-10-02 13:12 +0000

1"""AI: Recipe that converts taut-style test files to Python's unittest style.""" 

2 

3import re 

4import textwrap 

5from datetime import datetime 

6from pathlib import Path 

7 

8import test_data.test_class as tst_class 

9import test_data.test_insert as tst_insert 

10from renaissance.recipes.python_refactoring import PythonRefactoring 

11from renaissance.syntax_tree.match_finder import match_pattern 

12from renaissance.syntax_tree.semantic_kind import SemanticKind 

13 

14 

15class TautToPythonUnittest(PythonRefactoring): 

16 """AI: Recipe that converts taut-style test files to Python's unittest style.""" 

17 

18 def __init__(self, file): 

19 """AI: Prepare a refactoring processor that converts taut-style tests to Python unittest style.""" 

20 super().__init__(file) 

21 self.white_list_reg = r"_test|_unittest|_tests" 

22 self.black_list_reg = r"_migrated|_after|_original" 

23 self.comp = "ABCD" 

24 

25 def run(self): 

26 """AI: Apply the full sequence of taut-to-unittest migration transforms and write the migrated file.""" 

27 if re.search(self.black_list_reg, self.filename): 

28 print(f"skipping: {Path(self.filename).resolve()}") 

29 return 

30 if not re.search(self.white_list_reg, self.filename): 

31 print(f"skipping: {Path(self.filename).resolve()}") 

32 return 

33 print(f"Taut to pyunit migration: {Path(self.filename).resolve()}") 

34 

35 # conditional refactor 

36 if "AP_core_functionality_test" in self.filename: 

37 self.insert_asserter() 

38 self.remove_assert_func() 

39 self.replace_unittest_with_asserter() 

40 self.assert_func() 

41 self.commit() 

42 

43 self.replace_mock() 

44 self.remove_stubserver() 

45 self.replace_taut() 

46 self.remove_decorator() 

47 self.add_self() 

48 self.convert_assert() 

49 self.convert_testdoubles_fun() 

50 

51 self.replace_log_compxtl("emrw") 

52 self.replace_log_compxtl("abcd") 

53 self.remove_taut_import() 

54 self.replace_taut_import() 

55 self.convert_setup_common() 

56 self.convert_teardown_common() 

57 self.convert_add_patcher() 

58 self.convert_teardown() 

59 self.convert_setup() 

60 self.convert_import_verify() 

61 self.shared_setup() 

62 self.commit() 

63 self.with_testdoubles() 

64 self.commit() 

65 

66 if self.root.signature.find("self.patches = []") > 0 or self.root.signature.find("patch.object") > 0: 

67 self.insert_patch_import() 

68 self.commit() 

69 

70 try: 

71 # result = insert_doc(result, "01-22-2026") 

72 with self.get_migrated_path(self.filename).open("w") as f: 

73 f.write(self.apply_to_string()) 

74 except FileNotFoundError: 

75 print(f"Error: File '{self.filename}' not found.") 

76 

77 def get_migrated_path(self, file_path): 

78 """Convert a file path to add '_migrated' before the extension. 

79 

80 Example: 'taut.py' -> 'taut_migrated.py' 

81 """ 

82 path = Path(file_path) 

83 return path.with_stem(f"{path.stem}_migrated") 

84 

85 def replace_taut(self): 

86 """Replace TAUT.TestCase by unittest.TestCase.""" 

87 [ 

88 self.replace("unittest.TestCase", node, False, False) 

89 for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) 

90 if node.name == "TAUT.TestCase" 

91 ] 

92 [ 

93 self.replace("unittest.TestCase", node, False, False) 

94 for node in self.find_semantic_kind(SemanticKind.NAME) 

95 if node.name == "TestCase" 

96 ] 

97 

98 def remove_decorator(self): 

99 """AI: Remove the @TAUT.log_stub decorator.""" 

100 [self.remove(node, False, False) for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name == "TAUT.log_stub"] 

101 

102 def add_self(self): 

103 """AI: Prefix known TAUT helper attribute names with "self." so they resolve as instance members.""" 

104 matching = [ 

105 "emrwxread", 

106 "emrwxwidxread", 

107 "emrwxviprxinterface", 

108 "whxstream2", 

109 "gtaaxtxmark", 

110 "mark_upd_q", 

111 "gtaaxtxmark", 

112 "gtaaxtxmrkxadv", 

113 "emrwxwidxcfg", 

114 "wlxload", 

115 "wlxclear", 

116 "gtmwxtxws", 

117 "emtlxt", 

118 "emtlxtxmc", 

119 "emtlxtxwid", 

120 "emrwxviprxtestlog", 

121 "emrwxviprxwh", 

122 ] 

123 parent_func = ["setUpCommon", "setUp"] 

124 [ 

125 self.replace("self." + node.name, node, False, False) 

126 for node in self.find_semantic_kind(SemanticKind.NAME) 

127 if node.name in matching 

128 ] 

129 

130 matching2 = ["EMRWxREAD.emrwxread"] 

131 [ 

132 self.replace("self." + node.name.split(".")[1], node, False, False) 

133 for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) 

134 if node.name in matching2 and node.get_ancestor("FunctionDef").name not in parent_func 

135 ] 

136 

137 def convert_assert(self): 

138 """AI: Replace TAUT-style assert_false/assert_true/assert_equal calls with unittest equivalents.""" 

139 [ 

140 self.replace("self.assertFalse", node, False, False) 

141 for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) 

142 if node.name == "self.assert_false" 

143 ] 

144 [ 

145 self.replace("self.assertTrue", node, False, False) 

146 for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) 

147 if node.name == "self.assert_true" 

148 ] 

149 [ 

150 self.replace("self.assertEqual", node, False, False) 

151 for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) 

152 if node.name == "self.assert_equal" 

153 ] 

154 

155 def remove_stubserver(self): 

156 """AI: Remove references to TAUT.StubServer.""" 

157 [self.remove(node, False, False) for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name == "TAUT.StubServer"] 

158 

159 def replace_mock(self): 

160 """AI: Replace mock.patch decorators with patch.""" 

161 [ 

162 self.replace("patch", node, False, False) 

163 for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) 

164 if node.name == "mock.patch" and node.parent.parent.name == "decorator_list" 

165 ] 

166 

167 def replace_log_compxtl(self, comp): 

168 """AI: Replace direct and assigned calls to {comp}xtl with a fake_{comp}xtl double, including TestDoubles blocks.""" 

169 func_call = self.pattern_factory.create_statements(f"{comp}xtl.$a($$bb)") 

170 for call in match_pattern(self.root.children, func_call): 

171 repl = call.signature.replace(f"{comp}xtl", f"fake_{comp}xtl") 

172 self.replace(repl, call.nodes, False, False) 

173 

174 assign = self.pattern_factory.create_statements(f"$c = {comp}xtl.$a($$bb)") 

175 for match in match_pattern(self.root.children, assign): 

176 repl = match.signature.replace(f"{comp}xtl", f"fake_{comp}xtl") 

177 self.replace(repl, match.nodes, False, False) 

178 self.commit() 

179 taut_test_doubles = self.pattern_factory.create_statements( 

180 f"with TAUT.TestDoubles({comp}xtl=Fake{comp.upper()}xTL(None)):\n log = TAUT.Logger()\n $$aa", 

181 ) 

182 for match in match_pattern(self.root.children, taut_test_doubles): 

183 repl = f"fake_{comp}xtl = Fake{comp.upper()}xTL(None)\n{match['$$aa']}" 

184 self.replace(repl, match.nodes, False, False) 

185 self.commit() 

186 

187 def remove_taut_import(self): 

188 """AI: Remove the "import TAUT" statement.""" 

189 taut_import = self.pattern_factory.create_statements("import TAUT\n") 

190 for match in match_pattern(self.root.children, taut_import): 

191 self.remove(match.nodes, False, False) 

192 

193 def replace_taut_import(self): 

194 """Replace mock by unittest.mock and using patch.""" 

195 mock = self.pattern_factory.create_statements("import mock\n") 

196 for match in match_pattern(self.root.children, mock): 

197 self.remove(match.nodes, False, False) 

198 

199 test_case = self.pattern_factory.create_statements("from TAUT import TestCase") 

200 for match in match_pattern(self.root.children, test_case): 

201 self.remove(match.nodes, False, False) 

202 import_taut = self.pattern_factory.create_statements("from TAUT import TestCase, TestDoubles") 

203 for match in match_pattern(self.root.children, import_taut): 

204 repl = "try:\n from unittest.mock import patch\nexcept ImportError:\n from mock import patch\n" 

205 self.replace(repl, match.nodes, False, False) 

206 import_doubles = self.pattern_factory.create_statements("from TAUT import TestDoubles") 

207 for match in match_pattern(self.root.children, import_doubles): 

208 repl = "try:\n from unittest.mock import patch\nexcept ImportError:\n from mock import patch\n" 

209 self.replace(repl, match.nodes, False, False) 

210 

211 def convert_tds(self): 

212 """AI: Convert self.tds.append(TestDoubles(...)) calls into add_patcher calls or direct stub assignments.""" 

213 tds = self.pattern_factory.create_statements("self.tds.append(TestDoubles($a, $b=$c))") 

214 for match in match_pattern(self.root.children, tds): 

215 repl = f"self.add_patcher({match['$a']}, '{match['$b']}', {match['$c']})" 

216 self.replace(repl, match.nodes, False, False) 

217 

218 tds2 = self.pattern_factory.create_statements("self.tds.append(TestDoubles($a=ImprovedStub($b)))") 

219 for match in match_pattern(self.root.children, tds2): 

220 repl = f"self.{match['$a']} = ImprovedStub({match['$b']})" 

221 self.replace(repl, match.nodes, False, False) 

222 

223 def convert_setup_common(self): 

224 """AI: Convert self.tds = [...] blocks into ImprovedStub setup plus patch.object patcher lists.""" 

225 insert_code = """ImprovedStub.ret_vals = {} 

226ImprovedStub.ret_vals_ex = {} 

227ImprovedStub.call_logs = {} 

228ImprovedStub.store_args = {} 

229 

230""" 

231 p_start = """for p in self.patchers: 

232 p.start() 

233""" 

234 tds_pattern = self.pattern_factory.create_statements("self.tds = [$$aa]") 

235 for match in match_pattern(self.root.children, tds_pattern): 

236 init_stubs = "" 

237 repl = "self.patchers = [\n" 

238 doubles_pattern = self.pattern_factory.create_expression("TestDoubles($a=ImprovedStub($b))") 

239 for matched_doubles in match_pattern(match.expansions["$$aa"], [doubles_pattern]): 

240 init_stubs += ( 

241 f"self.{matched_doubles.expansions['$a'][0]} = ImprovedStub({matched_doubles.expansions['$b'][0].signature})\n" 

242 ) 

243 interface_stub = self.find_import_interface(matched_doubles.expansions["$b"][0].signature) 

244 repl += ( 

245 f" patch.object({interface_stub}, '{matched_doubles.expansions['$a'][0]}', " 

246 f"self.{matched_doubles.expansions['$a'][0]}),\n" 

247 ) 

248 repl += "]\n\n" 

249 repl = insert_code + init_stubs + repl + p_start 

250 self.replace(repl, match.nodes, False, False) 

251 

252 def convert_teardown_common(self): 

253 """AI: Replace tearDownCommon's body with logic that stops all patchers.""" 

254 teardown_common = self.pattern_factory.create_statements("def tearDownCommon(self):\n $$aa") 

255 repl = """def tearDownCommon(self): 

256 for p in self.patchers: 

257 try: 

258 p.stop() 

259 except RuntimeError: 

260 pass 

261""" 

262 for match in match_pattern(self.root.children, teardown_common): 

263 self.replace(repl, match.nodes, False, False) 

264 

265 def convert_add_patcher(self): 

266 """AI: Insert an add_patcher helper method into tearDownCommon if one doesn't already exist.""" 

267 pattern = self.pattern_factory.create_statements("def tearDownCommon(self):\n $$aa") 

268 for match in match_pattern(self.root.children, pattern): 

269 patcher_pattern = [node for node in self.find_semantic_kind(SemanticKind.FUNCTION) if node.name == "add_patcher"] 

270 if len(patcher_pattern) == 0: 

271 self.insert_after(tst_class.insert_add_patcher, match.nodes) 

272 

273 def find_import_interface(self, name: str): 

274 """AI: Return the top-level module/interface name that name was imported from, or name itself.""" 

275 interface = name 

276 if name.islower(): 

277 node_list = [node for node in self.find_semantic_kind(SemanticKind.IMPORT) if node.name == name] 

278 if node_list: 

279 if node_list[0].parser_kind == "ImportFrom": 

280 interface = node_list[0].properties["module"] 

281 else: 

282 interface = node_list[0].name if node_list else name 

283 return interface.split(".")[0] 

284 

285 def convert_setup(self): 

286 """AI: Convert TAUT.TestDoubles-based setUp logic into patch/patch.object based setup with self.patches.""" 

287 # remove doubles init 

288 pattern1 = self.pattern_factory.create_statements("doubles = []") 

289 replacement = "self.patches = []" 

290 for match in match_pattern(self.root.children, pattern1): 

291 self.replace(replacement, match.nodes, False, False) 

292 

293 pattern2 = self.pattern_factory.create_statements("self.doubles = []") 

294 for match in match_pattern(self.root.children, pattern2): 

295 self.replace(replacement, match.nodes, False, False) 

296 

297 # convert doubles to patch 

298 self.convert_test_doubles("doubles.append(TAUT.TestDoubles($a=$b))") 

299 self.convert_test_doubles("self.doubles.append(TAUT.TestDoubles($a=$b))") 

300 

301 # convert doubles to patch.object 

302 insert_node = None 

303 pattern_outer = self.pattern_factory.create_statements("def setUp(self):\n $$aa") 

304 for setup_func in match_pattern(self.root.children, pattern_outer): 

305 pattern4 = self.pattern_factory.create_statements("doubles.append(TAUT.TestDoubles(module=$mod, $b=$c))") 

306 matched_pattern = match_pattern(setup_func.nodes, pattern4) 

307 for index, match in enumerate(matched_pattern): 

308 repl_pattern = ( 

309 f"self.patches.append(patch.object({match.expansions['$mod'][0].name}, " 

310 f"'{match.expansions['$b'][0]}', {match.expansions['$c'][0].signature}))" 

311 ) 

312 repl_pattern = repl_pattern.replace("context_stub", "self.context_stub") 

313 self.replace(repl_pattern, match.nodes, False, False) 

314 if index == len(matched_pattern) - 1: 

315 insert_node = match.nodes[-1] 

316 insert_code = """\nfor p in self.patches: 

317 p.start()""" 

318 self.insert_after(insert_code, insert_node, False, False) 

319 

320 pattern4_1 = self.pattern_factory.create_statements("self.doubles.append(TAUT.TestDoubles(module=$mod, $b=$c))") 

321 matched_pattern_1 = match_pattern(setup_func.nodes, pattern4_1) 

322 for index, match in enumerate(matched_pattern_1): 

323 repl_pattern = ( 

324 f"self.patches.append(patch.object({match.expansions['$mod'][0].name}, " 

325 f"'{match.expansions['$b'][0]}', {match.expansions['$c'][0].signature}))" 

326 ) 

327 repl_pattern = repl_pattern.replace("context_stub", "self.context_stub") 

328 self.replace(repl_pattern, match.nodes, False, False) 

329 if index == len(matched_pattern_1) - 1: 

330 insert_node = match.nodes[-1] 

331 insert_code = """\nfor p in self.patches: 

332 p.start()""" 

333 self.insert_after(insert_code, insert_node, False, False) 

334 

335 pattern5 = self.pattern_factory.create_statements("self.doubles = doubles") 

336 for match in match_pattern(self.root.children, pattern5): 

337 self.remove(match.nodes, False, False) 

338 self.commit() 

339 [ 

340 self.replace("self.context_stub", node, False, False) 

341 for node in self.find_semantic_kind(SemanticKind.NAME) 

342 if node.name == "context_stub" 

343 ] 

344 

345 def convert_teardown(self): 

346 """AI: Replace tearDown's body with logic that stops all patches.""" 

347 matched_pattern = self.pattern_factory.create_statements("def tearDown(self):\n $$aa") 

348 repl_pattern = """def tearDown(self): 

349 for p in self.patches: 

350 p.stop()""" 

351 for match in match_pattern(self.root.children, matched_pattern): 

352 self.replace(repl_pattern, match.nodes, False, False) 

353 

354 def refactor_teardown(self): 

355 """AI: Replace the doubles-exit teardown loop with patch.stopall() and reset context method attributes.""" 

356 self.comp = "abcd" 

357 pattern1 = self.pattern_factory.create_statements("for double in self.doubles:\n double.exit()") 

358 replace_pattern = "patch.stopall()" 

359 for match in match_pattern(self.root.children, pattern1): 

360 self.replace(replace_pattern, match.nodes, False, False) 

361 

362 insert_code = f"""{self.comp.upper()}xCONTEXT.{self.comp}xcontext.reset_method_attributes("start_wafer") 

363{self.comp.upper()}xCONTEXT.{self.comp}xcontext.reset_method_attributes("finish_wafer") 

364{self.comp.upper()}xCONTEXT.{self.comp}xcontext.reset_method_attributes("start_lot") 

365{self.comp.upper()}xCONTEXT.{self.comp}xcontext.reset_method_attributes("finish_lot") 

366 

367""" 

368 pattern2 = self.pattern_factory.create_statements("self._patch_readout_data_filler.stop()") 

369 for match in match_pattern(self.root.children, pattern2): 

370 self.insert_before(insert_code, match.nodes, False, False) 

371 

372 def convert_test_doubles(self, doubles: str): 

373 """AI: Convert matches of the given doubles-append pattern into self.patches.append(patch(...)) calls.""" 

374 mappings: dict[str, str] = { 

375 "emrmxcontext": "EMRMxCONTEXT", 

376 "acbdxcontext": "ACBDxCONTEXT", 

377 # Add more mappings here 

378 } 

379 doubles_pattern = self.pattern_factory.create_statements(doubles) 

380 for match in match_pattern(self.root.children, doubles_pattern): 

381 keyword = match.expansions["$a"][0] 

382 if match.expansions["$a"][0] in mappings: 

383 keyword = mappings[match.expansions["$a"][0]] 

384 repl_pattern = f"self.patches.append(patch('{keyword}.{match.expansions['$a'][0]}', {match.expansions['$b'][0].name}))" 

385 repl_pattern = repl_pattern.replace("context_stub", "self.context_stub") 

386 self.replace(repl_pattern, match.nodes, False, False) 

387 

388 def insert_patch_import(self): 

389 """AI: Insert a try/except import of unittest.mock.patch (falling back to mock.patch) if missing.""" 

390 insert = "\ntry:\n from unittest.mock import patch\nexcept ImportError:\n from mock import patch" 

391 insert_pattern = self.pattern_factory.create_statements(insert) 

392 if len(match_pattern(self.root.children, insert_pattern)) == 0: 

393 pattern = self.pattern_factory.create_statements("import unittest\n") 

394 for match in match_pattern(self.root.children, pattern): 

395 self.insert_after(insert, match.nodes, False, False) 

396 

397 def replace_taut_skip(self): 

398 """Replace @TAUT.skip_test by @unittest.skip.""" 

399 [self.replace("@unittest.skip", node) for node in self.find_semantic_kind(SemanticKind.ATTRIBUTE) if node.name == "TAUT.skip_test"] 

400 

401 def convert_import_verify(self): 

402 """AI: Replace self.import_and_verify_module('$a') calls with a real import plus an assertIsNotNone check.""" 

403 import_verify = self.pattern_factory.create_statements("self.import_and_verify_module('$a')") 

404 for match in match_pattern(self.root.children, import_verify): 

405 repl = f"import {match.expansions['$a'][0]}\nself.assertIsNotNone({match.expansions['$a'][0]})" 

406 self.replace(repl, match.nodes, False, False) 

407 

408 def with_testdoubles(self): 

409 """AI: Replace "with TAUT.TestDoubles(...)" blocks with equivalent "with patch.object(...)" blocks.""" 

410 pattern1 = self.pattern_factory.create_statements("with TAUT.TestDoubles(module=$a, $b=$c):\n $$ee") 

411 for match in match_pattern(self.root.children, pattern1): 

412 repl_pattern = f"with patch.object({match['$a']}, '{match['$b']}', new={match['$c']}):\n {match['$$ee']}" 

413 self.replace(repl_pattern, match.nodes, False, False) 

414 

415 def shared_setup(self): 

416 """AI: Rename a sharedSetUp method to setUp.""" 

417 setup_function = self.pattern_factory.create_statements("def sharedSetUp(self):\n $$stmts") 

418 for match in match_pattern(self.root.children, setup_function): 

419 repl = match.signature.replace("def sharedSetUp", " def setUp") 

420 self.replace(textwrap.dedent(repl), match.nodes, False, False) 

421 

422 def insert_class(self): 

423 """AI: Insert the Asserter helper class if it doesn't already exist in the file.""" 

424 class_pattern = self.pattern_factory.create_statements("class Asserter(unittest.TestCase):\n $$aa") 

425 if len(match_pattern(self.root.children, class_pattern)) == 0: 

426 insert_pattern = self.pattern_factory.create_statements("def b():\n $$bb") 

427 insert_code = tst_insert.insert_code 

428 for match in match_pattern(self.root.children, insert_pattern): 

429 self.insert_after(insert_code, match.nodes, False, False) 

430 

431 def insert_asserter(self): 

432 """AI: Insert the assert_double_equal helper function's replacement code.""" 

433 insert_pattern = self.pattern_factory.create_statements("def assert_double_equal($$arg, $$other=$$value):\n $$bb") 

434 insert_code = tst_insert.insert_code 

435 for match in match_pattern(self.root.children, insert_pattern): 

436 self.insert_after(insert_code, match.nodes, False, False) 

437 

438 def remove_assert_func(self): 

439 """AI: Remove the original assert_double_equal function definition.""" 

440 pattern = self.pattern_factory.create_statements("def assert_double_equal($$arg, $$other=$$value):\n $$bb") 

441 for match in match_pattern(self.root.children, pattern): 

442 self.remove(match.nodes, False, False) 

443 self.commit() 

444 

445 def replace_unittest_with_asserter(self): 

446 """AI: Make classes that use assert_raises/assert_double_equal extend Asserter instead of TAUT.TestCase.""" 

447 pattern = self.pattern_factory.create_statements("class $a(TAUT.TestCase):\n $$bb") 

448 for match in match_pattern(self.root.children, pattern): 

449 if match["$a"] != "Asserter" and ("assert_raises" in match["$$bb"] or "assert_double_equal" in match["$$bb"]): 

450 repl = f"{match.signature.replace('TAUT.TestCase', 'Asserter')}" 

451 self.replace(repl, match.nodes, False, False) 

452 self.commit() 

453 

454 def assert_func(self): 

455 """AI: Prefix assert_raises and assert_double_equal usages with "self.".""" 

456 matching = [ 

457 "assert_raises", 

458 "assert_double_equal", 

459 ] 

460 [ 

461 self.replace("self." + node.name, node, False, False) 

462 for node in self.find_semantic_kind(SemanticKind.NAME) 

463 if node.name in matching 

464 ] 

465 

466 def move_indent(self, indent): 

467 """AI: Replace a doubles-append call inside a function with an indented patch.object with-block.""" 

468 pattern1 = self.pattern_factory.create_statements("""def $a($$b): 

469 self.doubles.append(TAUT.TestDoubles($mod, $e, $f)) 

470 $$c""") 

471 for match in match_pattern(self.root.children, pattern1): 

472 double_pattern = f" self.doubles.append(TAUT.TestDoubles({match['$mod']}, {match['$e']}, {match['$f']}))\n" 

473 func_header_index = match.signature.index("):\n") 

474 repl = f""" with patch.object({match["$mod"]}, '{match["$e"]}', {match["$f"]}):\n""" 

475 replace_pattern = ( 

476 match.signature[: func_header_index + 3] + repl + textwrap.indent(match.signature[func_header_index + 3 :], indent) 

477 ) 

478 replace_pattern = replace_pattern.replace(double_pattern, "") 

479 self.replace(replace_pattern, match.nodes, False, False) 

480 

481 def convert_testdoubles_fun(self): 

482 """AI: Use this for taut migration, where the function pattern is found in a class.""" 

483 # case1 two TestDoubles are defined 

484 pattern1 = self.pattern_factory.create_statements("""def $a($$b): 

485 self.doubles.append( 

486 TAUT.TestDoubles( 

487 module=$mod1, $e1=$f1 

488 ) 

489 ) 

490 self.doubles.append( 

491 TAUT.TestDoubles( 

492 module=$mod2, $e2=$f2 

493 ) 

494 ) 

495 $$c 

496 """) 

497 for match in match_pattern(self.root.children, pattern1): 

498 double_pattern = f""" self.doubles.append( 

499 TAUT.TestDoubles( 

500 module={match["$mod1"]}, 

501 {match["$e1"]}={match["$f1"]}, 

502 ), 

503 ) 

504 self.doubles.append( 

505 TAUT.TestDoubles( 

506 module={match["$mod2"]}, 

507 {match["$e2"]}={match["$f2"]}, 

508 ), 

509 ) 

510""" 

511 func_header_index = match.signature.index("):\n") 

512 repl = f"""with patch.object({match["$mod1"]}, '{match["$e1"]}', {match["$f1"]}), \\ 

513 patch.object({match["$mod2"]}, '{match["$e2"]}', {match["$f2"]}):\n""" 

514 replace_pattern = ( 

515 match.signature[: func_header_index + 3] + textwrap.indent(repl, " ") + match.signature[func_header_index + 3 :] 

516 ) 

517 replace_pattern = replace_pattern.replace(textwrap.indent(double_pattern, " "), "") 

518 self.replace(replace_pattern, match.nodes, False, False) 

519 self.commit() 

520 

521 pattern2 = self.pattern_factory.create_statements("""def $a($$b): 

522 self.doubles.append( 

523 TAUT.TestDoubles( 

524 module=$mod, $e=$f 

525 ) 

526 ) 

527 $$c 

528 """) 

529 for match in match_pattern(self.root.children, pattern2): 

530 double_pattern = f""" self.doubles.append( 

531 TAUT.TestDoubles( 

532 module={match["$mod"]}, {match["$e"]}={match["$f"]} 

533 ) 

534 ) 

535""" 

536 double_pattern1 = f""" self.doubles.append( 

537 TAUT.TestDoubles( 

538 module={match["$mod"]}, 

539 {match["$e"]}={match["$f"]}, 

540 ) 

541 ) 

542""" 

543 func_header_index = match.signature.index("):\n") 

544 repl = f"""with patch.object({match["$mod"]}, '{match["$e"]}', {match["$f"]}):\n""" 

545 replace_pattern = ( 

546 match.signature[: func_header_index + 3] + textwrap.indent(repl, " ") + match.signature[func_header_index + 3 :] 

547 ) 

548 double_pattern2 = f" self.doubles.append(TAUT.TestDoubles(module={match['$mod']}, {match['$e']}={match['$f']}))\n" 

549 replace_pattern = replace_pattern.replace(textwrap.indent(double_pattern, " "), "") 

550 replace_pattern = replace_pattern.replace(textwrap.indent(double_pattern1, " "), "") 

551 replace_pattern = replace_pattern.replace(textwrap.indent(double_pattern2, " "), "") 

552 self.replace(replace_pattern, match.nodes, False, False) 

553 self.commit() 

554 

555 def refactor_testdoubles_fun(self): 

556 """Use this for unittest, where the function pattern is not found in a class.""" 

557 # case1 two TestDoubles are defined 

558 pattern1 = self.pattern_factory.create_statements("""def $a($$b): 

559 self.doubles.append( 

560 TAUT.TestDoubles( 

561 module=$mod1, $e1=$f1 

562 ) 

563 ) 

564 self.doubles.append( 

565 TAUT.TestDoubles( 

566 module=$mod2, $e2=$f2 

567 ) 

568 ) 

569 $$c""") 

570 for match in match_pattern(self.root.children, pattern1): 

571 double_pattern = f""" self.doubles.append( 

572 TAUT.TestDoubles( 

573 module={match["$mod1"]}, {match["$e1"]}={match["$f1"]} 

574 ) 

575 ) 

576 self.doubles.append( 

577 TAUT.TestDoubles( 

578 module={match["$mod2"]}, {match["$e2"]}={match["$f2"]} 

579 ) 

580 ) 

581""" 

582 func_header_index = match.signature.index("):\n") 

583 repl = f"""with patch.object({match["$mod1"]}, '{match["$e1"]}', {match["$f1"]}), \\ 

584 patch.object({match["$mod2"]}, '{match["$e2"]}', {match["$f2"]}): 

585 """ 

586 replace_pattern = match.signature[: func_header_index + 3] + textwrap.indent( 

587 repl + match.signature[func_header_index + 3 :], 

588 " ", 

589 ) 

590 replace_pattern = replace_pattern.replace(double_pattern, "") 

591 replace_pattern = replace_pattern.replace(textwrap.indent(double_pattern, " "), "") 

592 self.replace(replace_pattern, match.nodes, False, False) 

593 self.commit() 

594 pattern2 = self.pattern_factory.create_statements("""def $a($$b): 

595 self.doubles.append( 

596 TAUT.TestDoubles( 

597 module=$mod, $e=$f 

598 ) 

599 ) 

600 $$c 

601""") 

602 for match in match_pattern(self.root.children, pattern2): 

603 double_pattern = f""" self.doubles.append( 

604 TAUT.TestDoubles( 

605 module={match["$mod"]}, {match["$e"]}={match["$f"]} 

606 ) 

607 ) 

608""" 

609 func_header_index = match.signature.index("):\n") 

610 repl = f"""with patch.object({match["$mod"]}, '{match["$e"]}', {match["$f"]}):\n""" 

611 replace_pattern = match.signature[: func_header_index + 3] + textwrap.indent( 

612 repl + match.signature[func_header_index + 3 :], 

613 " ", 

614 ) 

615 replace_pattern = replace_pattern.replace(double_pattern, "") 

616 replace_pattern = replace_pattern.replace(textwrap.indent(double_pattern, " "), "") 

617 self.replace(replace_pattern, match.nodes, False, False) 

618 

619 def refactor_testdoubles_class(self): 

620 """AI: Convert a class-level TAUT.TestDoubles-based setUp/tearDown into patch.object based setup/teardown.""" 

621 pattern = self.pattern_factory.create_statements("""class $a(TAUT.TestCase): 

622 

623 def setUp(self): 

624 $$bb 

625 self.doubles = [] 

626 $$cc 

627 self.doubles.append( 

628 TAUT.TestDoubles( 

629 module=$mod1, 

630 $e1=$f1, 

631 ) 

632 ) 

633 self.doubles.append( 

634 TAUT.TestDoubles( 

635 module=$mod2, 

636 $e2=$f2, 

637 ) 

638 ) 

639 $$dd 

640 

641 def tearDown(self): 

642 $$gg 

643 for double in self.doubles: 

644 double.exit()""") 

645 

646 for match in match_pattern(self.root.children, pattern): 

647 replace_pattern = f"""class {match["$a"]}(unittest.TestCase): 

648 

649 def setUp(self): 

650{textwrap.indent(match["$$bb"], " ")} 

651{textwrap.indent(match["$$cc"], " ")} 

652 self.patches = [ 

653 patch.object({match["$mod1"]}, '{match["$e1"]}', {match["$f1"]}), 

654 patch.object({match["$mod2"]}, '{match["$e2"]}', {match["$f2"]}), 

655 ] 

656 for p in self.patches: 

657 p.start() 

658 

659{textwrap.indent(match["$$dd"], " ")} 

660 

661 def tearDown(self): 

662{textwrap.indent(match["$$gg"], " ")} 

663 for p in self.patches: 

664 p.stop()""" 

665 self.replace(replace_pattern, match.nodes, False, False) 

666 

667 def insert_doc_func(self): 

668 """AI: Insert a change-log comment before the existing copyright comment block.""" 

669 pattern = self.pattern_factory.create_statements("""# -----------------------------------------------------------------------------# 

670# # 

671# Copyright (c) 2016, XXXX Netherlands B.V. # 

672""") 

673 insert_code = get_change_comment() 

674 for match in match_pattern(self.root.children, pattern): 

675 self.insert_before(insert_code, match.nodes, False, False) 

676 

677 

678def insert_doc(content: str, date): 

679 """AI: Insert a change-log comment line just before the existing copyright comment block in content.""" 

680 pattern = r"# -+(#)?\n(#\s+#\n)?#\s+Copyright \(c\) \d{4}, XXXX" 

681 match = re.search(pattern, content) 

682 

683 if not match: 

684 print("Comment block not found.") 

685 return content 

686 

687 # Find the beginning of the line containing the comment 

688 position = match.start() 

689 line_start = content.rfind("\n", 0, position) + 1 

690 if line_start == 0: # If comment is at the beginning of the file 

691 line_start = 0 

692 

693 # Insert the new line before the comment block 

694 print(get_change_comment(date)) 

695 modified_content = content[:line_start] + get_change_comment(date) + "\n" + content[line_start:] 

696 return modified_content 

697 

698 

699def get_change_comment(date=None): 

700 """Generate a formatted change comment with today's date. 

701 

702 Args: 

703 date (str, optional): The date to use for the change comment, in "%m-%d-%Y" format. Defaults to today's date if None. 

704 

705 Returns: 

706 str: Formatted change comment string 

707 

708 """ 

709 change_id = "SWCHGxxxxxxxx" 

710 description = "Add assert_raises method to Asserter class." 

711 

712 # When no date provided, use today 

713 tz = datetime.UTC 

714 formatted_date = datetime.now(tz) if date is None else datetime.strptime(date, "%m-%d-%Y").astimezone(tz) 

715 

716 return f"# {formatted_date.strftime('%m-%d-%Y')} : {change_id} SBYN {description}"