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
« 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."""
3import re
4import textwrap
5from datetime import datetime
6from pathlib import Path
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
15class TautToPythonUnittest(PythonRefactoring):
16 """AI: Recipe that converts taut-style test files to Python's unittest style."""
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"
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()}")
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()
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()
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()
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()
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.")
77 def get_migrated_path(self, file_path):
78 """Convert a file path to add '_migrated' before the extension.
80 Example: 'taut.py' -> 'taut_migrated.py'
81 """
82 path = Path(file_path)
83 return path.with_stem(f"{path.stem}_migrated")
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 ]
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"]
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 ]
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 ]
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 ]
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"]
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 ]
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)
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()
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)
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)
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)
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)
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)
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 = {}
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)
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)
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)
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]
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)
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)
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))")
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)
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)
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 ]
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)
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)
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")
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)
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)
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)
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"]
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)
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)
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)
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)
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)
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()
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()
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 ]
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)
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()
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()
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)
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):
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
641 def tearDown(self):
642 $$gg
643 for double in self.doubles:
644 double.exit()""")
646 for match in match_pattern(self.root.children, pattern):
647 replace_pattern = f"""class {match["$a"]}(unittest.TestCase):
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()
659{textwrap.indent(match["$$dd"], " ")}
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)
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)
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)
683 if not match:
684 print("Comment block not found.")
685 return content
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
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
699def get_change_comment(date=None):
700 """Generate a formatted change comment with today's date.
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.
705 Returns:
706 str: Formatted change comment string
708 """
709 change_id = "SWCHGxxxxxxxx"
710 description = "Add assert_raises method to Asserter class."
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)
716 return f"# {formatted_date.strftime('%m-%d-%Y')} : {change_id} SBYN {description}"