From e99c1b636d99f926dbb6edd120fe15a9ec3cf5b6 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Tue, 3 Oct 2023 11:01:15 +0200 Subject: [PATCH 01/23] feat: minimize TryStar to Try --- pysource_minimize/_minimize.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 5f281a8..ea1cd62 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -29,7 +29,6 @@ def is_block(nodes): class Minimizer: def __init__(self, source, checker, progress_callback): - self.checker = checker self.progress_callback = progress_callback @@ -97,7 +96,7 @@ def replaced_node(node): return i return node_map[i] - def replaced_nodes(nodes): + def replaced_nodes(nodes, name): def replace(l): for i in l: if i not in replaced: @@ -120,7 +119,7 @@ def replace(l): result = list(replace([n.__index for n in nodes])) - if not result and block: + if not result and block and name not in ("orelse", "finalbody"): return [ast.Pass()] if block: @@ -136,7 +135,7 @@ def map_node(node): ): setattr(node, name, replaced[(node.__index, name)]) elif isinstance(child, list): - setattr(node, name, replaced_nodes(child)) + setattr(node, name, replaced_nodes(child, name)) else: setattr(node, name, replaced_node(child)) for child in ast.iter_child_nodes(node): @@ -338,7 +337,6 @@ def minimize_expr(self, node): elif isinstance( node, (ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp) ): - for gen in node.generators: if self.try_only(node, gen.iter): self.minimize_expr(gen.iter) @@ -550,7 +548,6 @@ def minimize_stmt(self, node): self.try_only_minimize(node, node.test, node.body, node.orelse) elif isinstance(node, (ast.With, ast.AsyncWith)): - for item in node.items: if self.try_only(node, item.context_expr): self.minimize(item.context_expr) @@ -579,6 +576,19 @@ def minimize_item(item: ast.withitem): self.minimize_optional(node.exc) elif isinstance(node, ast.Try) or (py311 and isinstance(node, ast.TryStar)): + try_star = py311 and isinstance(node, ast.TryStar) + + if try_star and self.try_node( + node, + ast.Try( + body=node.body, + handlers=node.handlers, + orelse=node.orelse, + finalbody=node.finalbody, + ), + ): + return + if self.try_only(node, node.body): self.minimize(node.body) return @@ -602,7 +612,7 @@ def minimize_item(item: ast.withitem): def minimize_except_handler(handler): self.minimize_list(handler.body, self.minimize_stmt) - if not handler.name: + if not handler.name and not try_star: self.minimize_optional(handler.type) self.minimize_list( @@ -622,7 +632,6 @@ def minimize_except_handler(handler): return elif isinstance(node, (ast.Import, ast.ImportFrom)): - self.minimize_list(node.names, lambda e: None, 1) elif isinstance(node, (ast.Global)): @@ -643,7 +652,6 @@ def minimize_except_handler(handler): elif isinstance(node, ast.Pass): pass else: - raise TypeError(node) # Stmt def minimize_type_param(self, node): @@ -652,7 +660,6 @@ def minimize_type_param(self, node): self.minimize_optional(node.bound) def minimize_lists(self, lists, terminals, minimal=0): - lists = list(zip(*lists)) max_remove = len(lists) - minimal @@ -680,7 +687,6 @@ def devide(l): else: remaining.append(l[0]) else: - mid = len(l) // 2 # remove in reverse order @@ -696,7 +702,6 @@ def devide(l): terminal(node) def minimize_list(self, stmts, terminal, minimal=0): - # return self.minimize_lists((stmts,),(terminal,),minimal=0) stmts = list(stmts) max_remove = len(stmts) - minimal @@ -720,7 +725,6 @@ def devide(l): else: remaining.append(l[0]) else: - mid = len(l) // 2 # remove in reverse order From 11f9002e67a2a3f40ec6c9c585b557e530ee7f6e Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Tue, 3 Oct 2023 14:28:18 +0200 Subject: [PATCH 02/23] test: implemented find needle test --- tests/conftest.py | 20 ++- ...b0db5889b95d88a7e79326997e620310_name_4.py | 8 -- ...9bdddd66c8f35635a8598775e06a7eec_name_0.py | 2 - ...6f7d62007c7fa25bb0b2713bac36896c_name_0.py | 2 - ...89174e71e2a8da4e5c9c3058aae1387b_name_3.py | 2 - ...aee741340384a0db48adb3f1d4fc0b10_name_1.py | 2 - ...2f4dfd07c9db496a8a0b809eeac0df63_name_0.py | 2 - ...bf8650b9a26043d88e44ed67f0798fe1_name_0.py | 2 - ...e911f36dc84c143901544c7684a38891_name_4.py | 2 - ...944a7448d75d553b9ea20349a91a25c4_name_4.py | 8 -- ...9abdfeaf579059205e3a44fa32a4370f_name_2.py | 3 - tests/test_checker.py | 82 ------------- tests/test_needle.py | 114 ++++++++++++++++++ 13 files changed, 133 insertions(+), 116 deletions(-) delete mode 100644 tests/node_name_samples/0e1f91af1b3a644e295741fafc63d68ab0db5889b95d88a7e79326997e620310_name_4.py delete mode 100644 tests/node_name_samples/0ff812be1859047590e667ce971b574d9bdddd66c8f35635a8598775e06a7eec_name_0.py delete mode 100644 tests/node_name_samples/18920e94957571fb8636eeab788282886f7d62007c7fa25bb0b2713bac36896c_name_0.py delete mode 100644 tests/node_name_samples/200ca2a92ec9500470b78f425a083a5789174e71e2a8da4e5c9c3058aae1387b_name_3.py delete mode 100644 tests/node_name_samples/afe35c3802106229be98522719c1f8c8aee741340384a0db48adb3f1d4fc0b10_name_1.py delete mode 100644 tests/node_name_samples/b277b4637e2d7257d41ce9f123aaa9eb2f4dfd07c9db496a8a0b809eeac0df63_name_0.py delete mode 100644 tests/node_name_samples/cb5ee765d78fa6306233ed4bea4e8749bf8650b9a26043d88e44ed67f0798fe1_name_0.py delete mode 100644 tests/node_name_samples/de851a926e11a7948455809a3c8c1d43e911f36dc84c143901544c7684a38891_name_4.py delete mode 100644 tests/node_name_samples/e2e484bac1ee0d8e886a8b1e8603b047944a7448d75d553b9ea20349a91a25c4_name_4.py delete mode 100644 tests/node_name_samples/f5d82871929d46ed5028ad6fa8c327cc9abdfeaf579059205e3a44fa32a4370f_name_2.py delete mode 100644 tests/test_checker.py create mode 100644 tests/test_needle.py diff --git a/tests/conftest.py b/tests/conftest.py index 02d3fb0..2bc2e4b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,24 @@ import pytest -@pytest.fixture(params=range(0)) +@pytest.fixture(params=range(100)) def seed(): return random.randrange(0, 100000000) + + + +def pytest_addoption(parser, pluginmanager): + parser.addoption( + "--generate-samples",action="store_true" + , help="Config file to use, defaults to %(default)s", + ) + +def pytest_sessionfinish(session, exitstatus): + if exitstatus==0 and session.config.option.generate_samples: + from .test_needle import generate_needle + + generate_needle() + + + # teardown_stuff + diff --git a/tests/node_name_samples/0e1f91af1b3a644e295741fafc63d68ab0db5889b95d88a7e79326997e620310_name_4.py b/tests/node_name_samples/0e1f91af1b3a644e295741fafc63d68ab0db5889b95d88a7e79326997e620310_name_4.py deleted file mode 100644 index 61c3706..0000000 --- a/tests/node_name_samples/0e1f91af1b3a644e295741fafc63d68ab0db5889b95d88a7e79326997e620310_name_4.py +++ /dev/null @@ -1,8 +0,0 @@ -try: - pass -except f'': - pass -else: - pass -finally: - name_4(name_2, name_0=name_4) \ No newline at end of file diff --git a/tests/node_name_samples/0ff812be1859047590e667ce971b574d9bdddd66c8f35635a8598775e06a7eec_name_0.py b/tests/node_name_samples/0ff812be1859047590e667ce971b574d9bdddd66c8f35635a8598775e06a7eec_name_0.py deleted file mode 100644 index 269d3fd..0000000 --- a/tests/node_name_samples/0ff812be1859047590e667ce971b574d9bdddd66c8f35635a8598775e06a7eec_name_0.py +++ /dev/null @@ -1,2 +0,0 @@ -with {name_4, name_0}: # type: ignore - name_0 \ No newline at end of file diff --git a/tests/node_name_samples/18920e94957571fb8636eeab788282886f7d62007c7fa25bb0b2713bac36896c_name_0.py b/tests/node_name_samples/18920e94957571fb8636eeab788282886f7d62007c7fa25bb0b2713bac36896c_name_0.py deleted file mode 100644 index a205a3b..0000000 --- a/tests/node_name_samples/18920e94957571fb8636eeab788282886f7d62007c7fa25bb0b2713bac36896c_name_0.py +++ /dev/null @@ -1,2 +0,0 @@ -with (name_3, name_0): # type: ignoresome text - [name_1 for name_0 in name_3] \ No newline at end of file diff --git a/tests/node_name_samples/200ca2a92ec9500470b78f425a083a5789174e71e2a8da4e5c9c3058aae1387b_name_3.py b/tests/node_name_samples/200ca2a92ec9500470b78f425a083a5789174e71e2a8da4e5c9c3058aae1387b_name_3.py deleted file mode 100644 index 6d072ac..0000000 --- a/tests/node_name_samples/200ca2a92ec9500470b78f425a083a5789174e71e2a8da4e5c9c3058aae1387b_name_3.py +++ /dev/null @@ -1,2 +0,0 @@ -with {name_4, name_3}: # type: ignoresome text - name_5 < name_3 \ No newline at end of file diff --git a/tests/node_name_samples/afe35c3802106229be98522719c1f8c8aee741340384a0db48adb3f1d4fc0b10_name_1.py b/tests/node_name_samples/afe35c3802106229be98522719c1f8c8aee741340384a0db48adb3f1d4fc0b10_name_1.py deleted file mode 100644 index 8ea2a7c..0000000 --- a/tests/node_name_samples/afe35c3802106229be98522719c1f8c8aee741340384a0db48adb3f1d4fc0b10_name_1.py +++ /dev/null @@ -1,2 +0,0 @@ -with {name_2, name_1}: # type: ignoresome text - name_1 \ No newline at end of file diff --git a/tests/node_name_samples/b277b4637e2d7257d41ce9f123aaa9eb2f4dfd07c9db496a8a0b809eeac0df63_name_0.py b/tests/node_name_samples/b277b4637e2d7257d41ce9f123aaa9eb2f4dfd07c9db496a8a0b809eeac0df63_name_0.py deleted file mode 100644 index 774d158..0000000 --- a/tests/node_name_samples/b277b4637e2d7257d41ce9f123aaa9eb2f4dfd07c9db496a8a0b809eeac0df63_name_0.py +++ /dev/null @@ -1,2 +0,0 @@ -with name_3, (name_2, name_0): # type: ignore - name_0 \ No newline at end of file diff --git a/tests/node_name_samples/cb5ee765d78fa6306233ed4bea4e8749bf8650b9a26043d88e44ed67f0798fe1_name_0.py b/tests/node_name_samples/cb5ee765d78fa6306233ed4bea4e8749bf8650b9a26043d88e44ed67f0798fe1_name_0.py deleted file mode 100644 index c5ac295..0000000 --- a/tests/node_name_samples/cb5ee765d78fa6306233ed4bea4e8749bf8650b9a26043d88e44ed67f0798fe1_name_0.py +++ /dev/null @@ -1,2 +0,0 @@ -with (name_4, name_0), name_0: # type: ignore - pass \ No newline at end of file diff --git a/tests/node_name_samples/de851a926e11a7948455809a3c8c1d43e911f36dc84c143901544c7684a38891_name_4.py b/tests/node_name_samples/de851a926e11a7948455809a3c8c1d43e911f36dc84c143901544c7684a38891_name_4.py deleted file mode 100644 index de48597..0000000 --- a/tests/node_name_samples/de851a926e11a7948455809a3c8c1d43e911f36dc84c143901544c7684a38891_name_4.py +++ /dev/null @@ -1,2 +0,0 @@ -with (name_3, name_4), True as name_4: # type: ignoresome text - pass \ No newline at end of file diff --git a/tests/node_name_samples/e2e484bac1ee0d8e886a8b1e8603b047944a7448d75d553b9ea20349a91a25c4_name_4.py b/tests/node_name_samples/e2e484bac1ee0d8e886a8b1e8603b047944a7448d75d553b9ea20349a91a25c4_name_4.py deleted file mode 100644 index 39fb7c9..0000000 --- a/tests/node_name_samples/e2e484bac1ee0d8e886a8b1e8603b047944a7448d75d553b9ea20349a91a25c4_name_4.py +++ /dev/null @@ -1,8 +0,0 @@ -try: - pass -except name_2.name_1(name_4, [name_4], name_5): - pass -else: - [[name_4]] -finally: - pass \ No newline at end of file diff --git a/tests/node_name_samples/f5d82871929d46ed5028ad6fa8c327cc9abdfeaf579059205e3a44fa32a4370f_name_2.py b/tests/node_name_samples/f5d82871929d46ed5028ad6fa8c327cc9abdfeaf579059205e3a44fa32a4370f_name_2.py deleted file mode 100644 index 46008f5..0000000 --- a/tests/node_name_samples/f5d82871929d46ed5028ad6fa8c327cc9abdfeaf579059205e3a44fa32a4370f_name_2.py +++ /dev/null @@ -1,3 +0,0 @@ -with {name_4, name_2}: # type: ignore - with name_3: # type: ignore - name_2 \ No newline at end of file diff --git a/tests/test_checker.py b/tests/test_checker.py deleted file mode 100644 index 20d58a0..0000000 --- a/tests/test_checker.py +++ /dev/null @@ -1,82 +0,0 @@ -import ast -import hashlib -from pathlib import Path - -import pytest -from pysource_codegen import generate - -from pysource_minimize import minimize - -sample_dir = Path(__file__).parent / "node_name_samples" - -sample_dir.mkdir(exist_ok=True) - - -def check_sample(source, name): - def count(source): - return sum( - node.id == name - for node in ast.walk(ast.parse(source)) - if isinstance(node, ast.Name) - ) - - num = count(source) - - print("check", name, num) - - def checker(source): - # print() - # print("source:") - # print(source) - return count(source) >= num - - new_source = minimize(source, checker) - assert count(new_source) == num - - # new_tree = ast.parse(new_source) - # assert all(node.id != name for node in ast.walk(new_tree) if isinstance(node, ast.Name)) - - -@pytest.mark.parametrize( - "file", [pytest.param(f, id=f.stem) for f in sample_dir.glob("*.py")] -) -def test_files(file): - name = file.stem.split("_", 1)[1] - source = file.read_text() - print(source) - check_sample(source, name) - - -def test_file(seed): - source = generate(seed, node_limit=10000, depth_limit=6) - tree = ast.parse(source) - - names = {node.id for node in ast.walk(tree) if isinstance(node, ast.Name)} - - for name in sorted(names): - try: - check_sample(source, name) - except: - - print("minimize") - - def checker(source): - try: - check_sample(source, name) - except: - return True - return False - - try: - new_source = minimize(source, checker) - ( - sample_dir - / f"{hashlib.sha256(new_source.encode('utf-8')).hexdigest()}_{name}.py" - ).write_text(new_source) - except: - ( - sample_dir - / f"{hashlib.sha256(source.encode('utf-8')).hexdigest()}_{name}.py" - ).write_text(source) - - raise diff --git a/tests/test_needle.py b/tests/test_needle.py new file mode 100644 index 0000000..b8e2ab0 --- /dev/null +++ b/tests/test_needle.py @@ -0,0 +1,114 @@ +import ast +import hashlib +from pathlib import Path + +import pytest +from pysource_codegen import generate + +from pysource_minimize import minimize +import itertools +import random + +sample_dir = Path(__file__).parent / "needle_samples" + +sample_dir.mkdir(exist_ok=True) + +needle_name="needle_17597" + +def contains_one_needle(source): + return needle_count(source)==1 + + +def needle_count(source): + tree = ast.parse(source) + + return sum( + isinstance(node,ast.Name) and node.id==needle_name + for node in ast.walk(tree)) + +def try_find_needle(source): + assert contains_one_needle(source) + + new_source = minimize(source, contains_one_needle) + assert new_source == needle_name + + +@pytest.mark.parametrize( + "file", [pytest.param(f, id=f.stem) for f in sample_dir.glob("*.py")] +) +def test_needle(file): + source = file.read_text() + print(source) + try_find_needle(source) + + +class HideNeedle(ast.NodeTransformer): + def __init__(self,num): + self.num=num + self.index=0 + self.needle_hidden=False + + def generic_visit(self, node: ast.AST) -> ast.AST: + if isinstance(node,ast.expr): + if self.num==self.index and not self.needle_hidden: + self.index+=1 + self.needle_hidden=True + return ast.Name(id=needle_name) + self.index+=1 + + return super().generic_visit(node) + + +def generate_needle(): + seed= random.randrange(0, 100000000) + + source = generate(seed, node_limit=10000, depth_limit=6) + + for i in itertools.count(): + original_tree = ast.parse(source) + + hide_needle=HideNeedle(i) + needle_tree=hide_needle.visit(original_tree) + + if not hide_needle.needle_hidden: + break + + try: + needle_source=ast.unparse(needle_tree) + compile(needle_source,"","exec") + except: + print("skip this needle") + continue + + assert contains_one_needle(needle_source) + + try: + try_find_needle(needle_source) + except: + + print("minimize") + + def checker(source): + if needle_count(source)!=1: + return False + try: + try_find_needle(source) + except: + return True + return False + + try: + new_source = minimize(needle_source, checker) + print(new_source) + ( + sample_dir + / f"{hashlib.sha256(new_source.encode('utf-8')).hexdigest()}.py" + ).write_text(new_source) + except: + print("minimize failed") + ( + sample_dir + / f"{hashlib.sha256(needle_source.encode('utf-8')).hexdigest()}.py" + ).write_text(source) + + raise From bd0166a172e0fc3a2e6924ab7338004d2d8194d8 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Tue, 3 Oct 2023 21:47:40 +0200 Subject: [PATCH 03/23] fix: used needle_samples to find some issues --- pyproject.toml | 1 + pysource_minimize/_minimize.py | 129 ++++++++++++++---- tests/conftest.py | 13 +- ...126dc314ebe86a1524778bd0f8282f9efe3f50c.py | 2 + ...a0691682514f4d2e38e7ae2231dd9b71d66f8bd.py | 1 + ...3138a6db157ad30c60ab250811e0e3ced2b356b.py | 1 + ...513c19b0c0024f2d4231f7c963928c4a1ac21bc.py | 3 + ...c1e8cc6268187e08d1a6704b8e46f4031b18a2a.py | 1 + ...112e071e40bb113e23db1bd7cb7ca03f4f01d35.py | 1 + ...55a85551de8c87f85dfd228d3f414c7dacfbfc0.py | 2 + ...c8221d66b73f32d97dc26a965662f9b23d1930b.py | 1 + ...0d2e36f365501f64f9faba721bf366f6cdedd7b.py | 3 + ...40d91a2d7d0e1d25d5d8e92348a3efa54819566.py | 2 + ...ba5540e538f0bfc24f940706d4cb4c4f4d2ce60.py | 1 + ...0b973bedd6f4cae794bb24dfce5f1c53cca8fa6.py | 1 + ...a55d03f8bfc65f38f3ac64ebb231818048cf529.py | 2 + ...e9aa32fca703d3d648914dc192c3e385fef7e40.py | 1 + ...491a69193d7830d53935621af8829260285aca3.py | 1 + ...f57c4cb8ee379053b012403ad29262409628326.py | 2 + ...d9dc61bb0f7cacb75d911392afafd95a2d3e473.py | 1 + ...3c0d73f4dbd5152be473ebe948afc6cb423e8be.py | 2 + tests/test_needle.py | 53 ++++--- tests/test_remove_one.py | 5 +- 23 files changed, 173 insertions(+), 56 deletions(-) create mode 100644 tests/needle_samples/0766377bba7e1183bc6b60a00126dc314ebe86a1524778bd0f8282f9efe3f50c.py create mode 100644 tests/needle_samples/096c69226f40ed288f009989ba0691682514f4d2e38e7ae2231dd9b71d66f8bd.py create mode 100644 tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py create mode 100644 tests/needle_samples/135a1e0ef5d277ee4b3f1f222513c19b0c0024f2d4231f7c963928c4a1ac21bc.py create mode 100644 tests/needle_samples/1dc8d6fa0614ac3a102613c35c1e8cc6268187e08d1a6704b8e46f4031b18a2a.py create mode 100644 tests/needle_samples/2c9dd5384c1bf4c07d7030826112e071e40bb113e23db1bd7cb7ca03f4f01d35.py create mode 100644 tests/needle_samples/397c83ef84012357cd329f5be55a85551de8c87f85dfd228d3f414c7dacfbfc0.py create mode 100644 tests/needle_samples/3c81be9d7688f7e002c561de7c8221d66b73f32d97dc26a965662f9b23d1930b.py create mode 100644 tests/needle_samples/44bf672ae0eb3d411f6386f580d2e36f365501f64f9faba721bf366f6cdedd7b.py create mode 100644 tests/needle_samples/5f3166733173433a642358d6640d91a2d7d0e1d25d5d8e92348a3efa54819566.py create mode 100644 tests/needle_samples/7f4a88311276414dc959202b2ba5540e538f0bfc24f940706d4cb4c4f4d2ce60.py create mode 100644 tests/needle_samples/9bc324f232f61f3df1bcd711b0b973bedd6f4cae794bb24dfce5f1c53cca8fa6.py create mode 100644 tests/needle_samples/b4d76bcfa10ca9179203b6581a55d03f8bfc65f38f3ac64ebb231818048cf529.py create mode 100644 tests/needle_samples/bf24417717af32f5b6999aab2e9aa32fca703d3d648914dc192c3e385fef7e40.py create mode 100644 tests/needle_samples/cb57cbdece6733a706bd490f1491a69193d7830d53935621af8829260285aca3.py create mode 100644 tests/needle_samples/d3b703cdc529a83dbd9be151af57c4cb8ee379053b012403ad29262409628326.py create mode 100644 tests/needle_samples/d995f259dbef7f14c446109fbd9dc61bb0f7cacb75d911392afafd95a2d3e473.py create mode 100644 tests/needle_samples/e5d74f55a84871ca3b93c4d143c0d73f4dbd5152be473ebe948afc6cb423e8be.py diff --git a/pyproject.toml b/pyproject.toml index 081f64e..34389fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ data_file = "$TOP/.coverage" [tool.black] force-exclude = "tests/.*_samples" +skip_magic_trailing_comma = true [tool.mypy] exclude="tests/.*_samples" diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index ea1cd62..f18b072 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -2,6 +2,8 @@ import copy import os import sys +from typing import List +from typing import Union try: from ast import unparse @@ -27,6 +29,18 @@ def is_block(nodes): ) +def arguments( + node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda] +) -> List[ast.arg]: + args = node.args + l = [*args.args, args.vararg, *args.kwonlyargs, args.kwarg] + + if sys.version_info >= (3, 8): + l += args.posonlyargs + + return [arg for arg in l if arg is not None] + + class Minimizer: def __init__(self, source, checker, progress_callback): self.checker = checker @@ -246,6 +260,12 @@ def minimize_expr(self, node): if self.try_only(node, node.left): self.minimize(node.left) return + + for comp in node.comparators: + if self.try_only(node, comp): + self.minimize(comp) + return + self.minimize_lists( (node.ops, node.comparators), (lambda _: None, self.minimize) ) @@ -254,20 +274,38 @@ def minimize_expr(self, node): self.try_only_minimize(node, node.value, node.slice) elif isinstance(node, ast.FormattedValue): + if ( + isinstance(node.format_spec, ast.JoinedStr) + and len(node.format_spec.values) == 1 + and self.try_only(node, node.format_spec.values[0]) + ): + self.minimize(node.format_spec.values[0]) + return + self.try_none(node.format_spec) self.minimize_expr(node.value) elif isinstance(node, ast.JoinedStr): + if ( + len(node.values) == 1 + and isinstance(node.values[0], ast.FormattedValue) + and self.try_only(node, node.values[0].value) + ): + self.minimize(node.values[0].value) + return + self.minimize(node.values) # todo minimize values elif isinstance(node, ast.Slice): self.try_only_minimize(node, node.lower, node.upper, node.step) elif isinstance(node, ast.Lambda): + if self.try_only_minimize(node, node.body): return - self.minimize_args(node.args) + if self.minimize_args_of(node): + return elif isinstance(node, ast.UnaryOp): self.try_only_minimize(node, node.operand) @@ -338,31 +376,30 @@ def minimize_expr(self, node): node, (ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp) ): for gen in node.generators: + if self.try_only(node, gen.target): + self.minimize_expr(gen.target) + return + if self.try_only(node, gen.iter): self.minimize_expr(gen.iter) return - if isinstance(node, ast.DictComp): - if self.try_only(node, node.key): - self.minimize_expr(node.key) - return + for if_ in gen.ifs: + if self.try_only(node, if_): + self.minimize_expr(if_) + return - if self.try_only(node, node.value): - self.minimize_expr(node.value) + if isinstance(node, ast.DictComp): + if self.try_only_minimize(node, node.key, node.value): return - - self.minimize_expr(node.key) - self.minimize_expr(node.value) else: - if self.try_only(node, node.elt): - self.minimize_expr(node.elt) + if self.try_only_minimize(node, node.elt): return - self.minimize_expr(node.elt) self.minimize_list(node.generators, self.minimize_comprehension, 1) elif isinstance(node, ast.NamedExpr): - self.try_only_minimize(node, node.value) + self.try_only_minimize(node, node.target, node.value) else: raise TypeError(node) # Expr @@ -410,7 +447,19 @@ def minimize_pattern(pattern): minimize_pattern(c.pattern) - def minimize_args(self, args): + def minimize_args_of(self, func): + args = func.args + + for child in [ + *[arg.annotation for arg in arguments(func)], + *func.args.defaults, + *func.args.kw_defaults, + getattr(func, "returns", None), + ]: + if child is not None and self.try_only(func, child): + self.minimize(child) + return True + def minimize_arg(arg: ast.arg): if arg.annotation is not None and not self.try_none(arg.annotation): self.minimize(arg.annotation) @@ -435,6 +484,8 @@ def minimize_arg(arg: ast.arg): if args.kwarg is not None and not self.try_none(args.kwarg): minimize_arg(args.kwarg) + return False + def minimize_stmt(self, node): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): if self.try_only_minimize(node, node.decorator_list): @@ -460,7 +511,8 @@ def minimize_stmt(self, node): if self.try_only(node, node.body): return - self.minimize_args(node.args) + if self.minimize_args_of(node): + return if sys.version_info >= (3, 12): self.minimize_list(node.type_params, self.minimize_type_param) @@ -499,26 +551,30 @@ def minimize_stmt(self, node): self.try_only_minimize(node, node.value) elif isinstance(node, ast.Delete): + if len(node.targets) == 1 and self.try_only(node, node.targets[0]): + self.minimize(node.targets[0]) + return + self.minimize_list(node.targets, self.minimize, 1) elif isinstance(node, ast.Assign): self.try_only_minimize(node, node.value, node.targets) - # todo minimize targets - elif isinstance(node, ast.AugAssign): - self.minimize(node.target) - self.try_only_minimize(node, node.value) - # todo minimize target + self.try_only_minimize(node, node.target, node.value) elif isinstance(node, ast.AnnAssign): + if node.value is None or not self.try_node( node, ast.Assign(targets=[node.target], value=node.value) ): - self.try_only_minimize(node, node.value, node.annotation) - # todo minimize target + self.try_only_minimize(node, node.target, node.value, node.annotation) elif isinstance(node, (ast.For, ast.AsyncFor)): + if self.try_only(node, node.target): + self.minimize(node.target) + return + self.minimize_list(node.body, self.minimize_stmt) body = self.get_ast(node) if not any( @@ -548,21 +604,42 @@ def minimize_stmt(self, node): self.try_only_minimize(node, node.test, node.body, node.orelse) elif isinstance(node, (ast.With, ast.AsyncWith)): + + if self.try_only_minimize(node, node.body): + return + for item in node.items: if self.try_only(node, item.context_expr): self.minimize(item.context_expr) return + if item.optional_vars is not None and self.try_only( + node, item.optional_vars + ): + self.minimize(item.optional_vars) + return + def minimize_item(item: ast.withitem): self.minimize(item.context_expr) self.minimize_optional(item.optional_vars) self.minimize_list(node.items, minimize_item, minimal=1) - self.minimize_list(node.body, self.minimize_stmt) - elif py310 and isinstance(node, ast.Match): - self.minimize(node.subject) + if self.try_only_minimize(node, node.subject): + return + + for case_ in node.cases: + for e in [case_.guard, case_.body]: + if e is not None and self.try_only(node, e): + self.minimize(e) + return + + if isinstance(case_.pattern, ast.MatchValue): + if self.try_only(node, case_.pattern.value): + self.minimize(case_.pattern.value) + return + self.minimize_list(node.cases, self.minimize_match_case, 1) elif isinstance(node, ast.Raise): diff --git a/tests/conftest.py b/tests/conftest.py index 2bc2e4b..fe516c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,24 +3,23 @@ import pytest -@pytest.fixture(params=range(100)) +@pytest.fixture(params=range(0)) def seed(): return random.randrange(0, 100000000) - def pytest_addoption(parser, pluginmanager): parser.addoption( - "--generate-samples",action="store_true" - , help="Config file to use, defaults to %(default)s", + "--generate-samples", + action="store_true", + help="Config file to use, defaults to %(default)s", ) + def pytest_sessionfinish(session, exitstatus): - if exitstatus==0 and session.config.option.generate_samples: + if exitstatus == 0 and session.config.option.generate_samples: from .test_needle import generate_needle generate_needle() - # teardown_stuff - diff --git a/tests/needle_samples/0766377bba7e1183bc6b60a00126dc314ebe86a1524778bd0f8282f9efe3f50c.py b/tests/needle_samples/0766377bba7e1183bc6b60a00126dc314ebe86a1524778bd0f8282f9efe3f50c.py new file mode 100644 index 0000000..030481f --- /dev/null +++ b/tests/needle_samples/0766377bba7e1183bc6b60a00126dc314ebe86a1524778bd0f8282f9efe3f50c.py @@ -0,0 +1,2 @@ +def name_3() -> needle_17597: + pass \ No newline at end of file diff --git a/tests/needle_samples/096c69226f40ed288f009989ba0691682514f4d2e38e7ae2231dd9b71d66f8bd.py b/tests/needle_samples/096c69226f40ed288f009989ba0691682514f4d2e38e7ae2231dd9b71d66f8bd.py new file mode 100644 index 0000000..58caf28 --- /dev/null +++ b/tests/needle_samples/096c69226f40ed288f009989ba0691682514f4d2e38e7ae2231dd9b71d66f8bd.py @@ -0,0 +1 @@ +needle_17597 ^= name_4 \ No newline at end of file diff --git a/tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py b/tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py new file mode 100644 index 0000000..a8b6ee3 --- /dev/null +++ b/tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py @@ -0,0 +1 @@ +lambda *, name_5=needle_17597: name_5 \ No newline at end of file diff --git a/tests/needle_samples/135a1e0ef5d277ee4b3f1f222513c19b0c0024f2d4231f7c963928c4a1ac21bc.py b/tests/needle_samples/135a1e0ef5d277ee4b3f1f222513c19b0c0024f2d4231f7c963928c4a1ac21bc.py new file mode 100644 index 0000000..00ab8ac --- /dev/null +++ b/tests/needle_samples/135a1e0ef5d277ee4b3f1f222513c19b0c0024f2d4231f7c963928c4a1ac21bc.py @@ -0,0 +1,3 @@ +match needle_17597: + case name_1.name_3: + pass \ No newline at end of file diff --git a/tests/needle_samples/1dc8d6fa0614ac3a102613c35c1e8cc6268187e08d1a6704b8e46f4031b18a2a.py b/tests/needle_samples/1dc8d6fa0614ac3a102613c35c1e8cc6268187e08d1a6704b8e46f4031b18a2a.py new file mode 100644 index 0000000..57f1aed --- /dev/null +++ b/tests/needle_samples/1dc8d6fa0614ac3a102613c35c1e8cc6268187e08d1a6704b8e46f4031b18a2a.py @@ -0,0 +1 @@ +name_2 <= needle_17597 \ No newline at end of file diff --git a/tests/needle_samples/2c9dd5384c1bf4c07d7030826112e071e40bb113e23db1bd7cb7ca03f4f01d35.py b/tests/needle_samples/2c9dd5384c1bf4c07d7030826112e071e40bb113e23db1bd7cb7ca03f4f01d35.py new file mode 100644 index 0000000..47fbb5b --- /dev/null +++ b/tests/needle_samples/2c9dd5384c1bf4c07d7030826112e071e40bb113e23db1bd7cb7ca03f4f01d35.py @@ -0,0 +1 @@ +f'{needle_17597!s}' \ No newline at end of file diff --git a/tests/needle_samples/397c83ef84012357cd329f5be55a85551de8c87f85dfd228d3f414c7dacfbfc0.py b/tests/needle_samples/397c83ef84012357cd329f5be55a85551de8c87f85dfd228d3f414c7dacfbfc0.py new file mode 100644 index 0000000..11fcf4f --- /dev/null +++ b/tests/needle_samples/397c83ef84012357cd329f5be55a85551de8c87f85dfd228d3f414c7dacfbfc0.py @@ -0,0 +1,2 @@ +async def name_0(*, name_2=needle_17597): + pass \ No newline at end of file diff --git a/tests/needle_samples/3c81be9d7688f7e002c561de7c8221d66b73f32d97dc26a965662f9b23d1930b.py b/tests/needle_samples/3c81be9d7688f7e002c561de7c8221d66b73f32d97dc26a965662f9b23d1930b.py new file mode 100644 index 0000000..0b6cda2 --- /dev/null +++ b/tests/needle_samples/3c81be9d7688f7e002c561de7c8221d66b73f32d97dc26a965662f9b23d1930b.py @@ -0,0 +1 @@ +[name_5 for needle_17597 in name_3] \ No newline at end of file diff --git a/tests/needle_samples/44bf672ae0eb3d411f6386f580d2e36f365501f64f9faba721bf366f6cdedd7b.py b/tests/needle_samples/44bf672ae0eb3d411f6386f580d2e36f365501f64f9faba721bf366f6cdedd7b.py new file mode 100644 index 0000000..8261233 --- /dev/null +++ b/tests/needle_samples/44bf672ae0eb3d411f6386f580d2e36f365501f64f9faba721bf366f6cdedd7b.py @@ -0,0 +1,3 @@ +match name_3: + case needle_17597.name_5: + pass \ No newline at end of file diff --git a/tests/needle_samples/5f3166733173433a642358d6640d91a2d7d0e1d25d5d8e92348a3efa54819566.py b/tests/needle_samples/5f3166733173433a642358d6640d91a2d7d0e1d25d5d8e92348a3efa54819566.py new file mode 100644 index 0000000..336011e --- /dev/null +++ b/tests/needle_samples/5f3166733173433a642358d6640d91a2d7d0e1d25d5d8e92348a3efa54819566.py @@ -0,0 +1,2 @@ +def name_5(name_4: needle_17597, /): + pass \ No newline at end of file diff --git a/tests/needle_samples/7f4a88311276414dc959202b2ba5540e538f0bfc24f940706d4cb4c4f4d2ce60.py b/tests/needle_samples/7f4a88311276414dc959202b2ba5540e538f0bfc24f940706d4cb4c4f4d2ce60.py new file mode 100644 index 0000000..353bd7f --- /dev/null +++ b/tests/needle_samples/7f4a88311276414dc959202b2ba5540e538f0bfc24f940706d4cb4c4f4d2ce60.py @@ -0,0 +1 @@ +f'{1!s:{needle_17597}}' \ No newline at end of file diff --git a/tests/needle_samples/9bc324f232f61f3df1bcd711b0b973bedd6f4cae794bb24dfce5f1c53cca8fa6.py b/tests/needle_samples/9bc324f232f61f3df1bcd711b0b973bedd6f4cae794bb24dfce5f1c53cca8fa6.py new file mode 100644 index 0000000..0843b0a --- /dev/null +++ b/tests/needle_samples/9bc324f232f61f3df1bcd711b0b973bedd6f4cae794bb24dfce5f1c53cca8fa6.py @@ -0,0 +1 @@ +(name_3 for name_1 in name_5 if needle_17597) \ No newline at end of file diff --git a/tests/needle_samples/b4d76bcfa10ca9179203b6581a55d03f8bfc65f38f3ac64ebb231818048cf529.py b/tests/needle_samples/b4d76bcfa10ca9179203b6581a55d03f8bfc65f38f3ac64ebb231818048cf529.py new file mode 100644 index 0000000..de032c8 --- /dev/null +++ b/tests/needle_samples/b4d76bcfa10ca9179203b6581a55d03f8bfc65f38f3ac64ebb231818048cf529.py @@ -0,0 +1,2 @@ +with name_0 as needle_17597: + pass \ No newline at end of file diff --git a/tests/needle_samples/bf24417717af32f5b6999aab2e9aa32fca703d3d648914dc192c3e385fef7e40.py b/tests/needle_samples/bf24417717af32f5b6999aab2e9aa32fca703d3d648914dc192c3e385fef7e40.py new file mode 100644 index 0000000..79ec26c --- /dev/null +++ b/tests/needle_samples/bf24417717af32f5b6999aab2e9aa32fca703d3d648914dc192c3e385fef7e40.py @@ -0,0 +1 @@ +needle_17597: name_1 \ No newline at end of file diff --git a/tests/needle_samples/cb57cbdece6733a706bd490f1491a69193d7830d53935621af8829260285aca3.py b/tests/needle_samples/cb57cbdece6733a706bd490f1491a69193d7830d53935621af8829260285aca3.py new file mode 100644 index 0000000..b855895 --- /dev/null +++ b/tests/needle_samples/cb57cbdece6733a706bd490f1491a69193d7830d53935621af8829260285aca3.py @@ -0,0 +1 @@ +del needle_17597 \ No newline at end of file diff --git a/tests/needle_samples/d3b703cdc529a83dbd9be151af57c4cb8ee379053b012403ad29262409628326.py b/tests/needle_samples/d3b703cdc529a83dbd9be151af57c4cb8ee379053b012403ad29262409628326.py new file mode 100644 index 0000000..38bddb2 --- /dev/null +++ b/tests/needle_samples/d3b703cdc529a83dbd9be151af57c4cb8ee379053b012403ad29262409628326.py @@ -0,0 +1,2 @@ +with name_4: + needle_17597 \ No newline at end of file diff --git a/tests/needle_samples/d995f259dbef7f14c446109fbd9dc61bb0f7cacb75d911392afafd95a2d3e473.py b/tests/needle_samples/d995f259dbef7f14c446109fbd9dc61bb0f7cacb75d911392afafd95a2d3e473.py new file mode 100644 index 0000000..41dc202 --- /dev/null +++ b/tests/needle_samples/d995f259dbef7f14c446109fbd9dc61bb0f7cacb75d911392afafd95a2d3e473.py @@ -0,0 +1 @@ +(needle_17597 := name_0) \ No newline at end of file diff --git a/tests/needle_samples/e5d74f55a84871ca3b93c4d143c0d73f4dbd5152be473ebe948afc6cb423e8be.py b/tests/needle_samples/e5d74f55a84871ca3b93c4d143c0d73f4dbd5152be473ebe948afc6cb423e8be.py new file mode 100644 index 0000000..36eeb93 --- /dev/null +++ b/tests/needle_samples/e5d74f55a84871ca3b93c4d143c0d73f4dbd5152be473ebe948afc6cb423e8be.py @@ -0,0 +1,2 @@ +for needle_17597 in name_5: + pass \ No newline at end of file diff --git a/tests/test_needle.py b/tests/test_needle.py index b8e2ab0..325ce72 100644 --- a/tests/test_needle.py +++ b/tests/test_needle.py @@ -1,36 +1,38 @@ import ast import hashlib +import itertools +import random from pathlib import Path import pytest from pysource_codegen import generate from pysource_minimize import minimize -import itertools -import random sample_dir = Path(__file__).parent / "needle_samples" sample_dir.mkdir(exist_ok=True) -needle_name="needle_17597" +needle_name = "needle_17597" + def contains_one_needle(source): - return needle_count(source)==1 + return needle_count(source) == 1 def needle_count(source): tree = ast.parse(source) return sum( - isinstance(node,ast.Name) and node.id==needle_name - for node in ast.walk(tree)) + isinstance(node, ast.Name) and node.id == needle_name for node in ast.walk(tree) + ) + def try_find_needle(source): assert contains_one_needle(source) new_source = minimize(source, contains_one_needle) - assert new_source == needle_name + assert new_source.strip() == needle_name @pytest.mark.parametrize( @@ -38,44 +40,51 @@ def try_find_needle(source): ) def test_needle(file): source = file.read_text() + + try: + compile(source, file, "exec") + except: + pytest.skip() + print(source) try_find_needle(source) class HideNeedle(ast.NodeTransformer): - def __init__(self,num): - self.num=num - self.index=0 - self.needle_hidden=False + def __init__(self, num): + self.num = num + self.index = 0 + self.needle_hidden = False def generic_visit(self, node: ast.AST) -> ast.AST: - if isinstance(node,ast.expr): - if self.num==self.index and not self.needle_hidden: - self.index+=1 - self.needle_hidden=True + if isinstance(node, ast.expr): + if self.num == self.index and not self.needle_hidden: + self.index += 1 + self.needle_hidden = True + print("replace", node, "with needle") return ast.Name(id=needle_name) - self.index+=1 + self.index += 1 return super().generic_visit(node) def generate_needle(): - seed= random.randrange(0, 100000000) + seed = random.randrange(0, 100000000) source = generate(seed, node_limit=10000, depth_limit=6) for i in itertools.count(): original_tree = ast.parse(source) - hide_needle=HideNeedle(i) - needle_tree=hide_needle.visit(original_tree) + hide_needle = HideNeedle(i) + needle_tree = hide_needle.visit(original_tree) if not hide_needle.needle_hidden: break try: - needle_source=ast.unparse(needle_tree) - compile(needle_source,"","exec") + needle_source = ast.unparse(needle_tree) + compile(needle_source, "", "exec") except: print("skip this needle") continue @@ -89,7 +98,7 @@ def generate_needle(): print("minimize") def checker(source): - if needle_count(source)!=1: + if needle_count(source) != 1: return False try: try_find_needle(source) diff --git a/tests/test_remove_one.py b/tests/test_remove_one.py index 1e36f1e..9e4cc76 100644 --- a/tests/test_remove_one.py +++ b/tests/test_remove_one.py @@ -152,9 +152,12 @@ def test_samples(file): except: pytest.skip() + print("source") + print(source) + result, source = try_remove_one(source) - print("source") + print("\nnew minimized") print(source) print("weights:") for n, v in node_weights(source): From 410af26673ebb31371c64192c7da65a1c4562587 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Tue, 3 Oct 2023 22:44:49 +0200 Subject: [PATCH 04/23] feat: support for 3.12 --- .github/workflows/ci.yml | 2 +- .gitignore | 2 + noxfile.py | 3 +- poetry.lock | 266 +++++------ pyproject.toml | 5 +- pysource_minimize/__init__.py | 4 + pysource_minimize/_minimize.py | 422 ++++++++++++------ tests/conftest.py | 40 +- ...4174f67f66594f9d621a709b0ad7fd5b7dad6a7.py | 2 + ...3138a6db157ad30c60ab250811e0e3ced2b356b.py | 1 - ...9423304fd037c3d306c8695a69af65a9f06a127.py | 1 + ...705c77c27c8adec0897398254cc98a2239bae15.py | 2 + ...7f217433c142951288d31cf2b4ddd213d9653ff.py | 2 + ...59b6f28d1bc7f72686c22fdc21bab81ca6a07ba.py | 2 + ...79ff254a81eb20ae047547eadd2906537be4501.py | 2 + ...4c6f5104c074dbbffe5b4619cb39ab2662442be.py | 1 + ...ced9b448a792b9fb6459d399a551260c671a30a.py | 1 + ...928f74f2d289753d3c05ac1f4e830e1200c9227.py | 4 + ...0844be6db23d43ac1484aa2b510cda10b4c955f.py | 2 + ...12bddf5b25170d21d4c028321c86adc54db5e85.py | 2 + ...b796a337a69626431d9c5c908cf9876bf110b18.py | 4 + ...c6e14769729512141792f872c68d33742fb1a5b.py | 2 + ...e32508cb51f0048de4f239942dd67f28b99faf4.py | 1 + ...f083efdad310fee270b14b2469c6cc2d85be905.py | 1 + ...5ca977d76388670ac7694316ac20fd05753a55b.py | 1 + ...553901f0694b860dcf2e7c28cde53604e6b6a41.py | 1 + ...d30cf7b7bb35580d4db6d1eec632e7a4611a69a.py | 2 + ...dec18a0f85ef85646f368a67be58f0bc96c3e78.py | 2 + ...fa2140d4951fb6d9d9a3cae6b1c48535a7c9ef6.py | 4 + ...2b9c19c9bb63a88a3bb81122cd9d542e9392cdb.py | 1 + ...c48bb4663b15256b4ca9093f8f5ed8ee7c8a883.py | 4 + ...ea66feca2f0c33a75b3a25f7286ee7aa2df319d.py | 2 + ...6a9acd218a9d80baf1d5f6ab91c8e5e50cc47f1.py | 1 + ...e0d132bf2c36fa3d459738de9ea14d08f9cad7c.py | 5 + ...3d5495ce51609f353cf4b025627d4c8945f7bed.py | 1 + ...6d01a017d891fe8cd377dd0cbf0771d7f283e85.py | 2 + ...ed61d418acee4dd2e34a8c2ac9b5e3035a3c9b5.py | 6 + ...d62318ce0a96179f85e13c783d4195474db3605.py | 2 + ...b367b945dcea01efca58e1b734f31b4613e6f69.py | 4 + tests/test_needle.py | 80 +++- tests/test_remove_one.py | 137 ++++-- tests/utils.py | 13 + 42 files changed, 696 insertions(+), 346 deletions(-) create mode 100644 tests/needle_samples/09e9e63c9378d4dc4985901994174f67f66594f9d621a709b0ad7fd5b7dad6a7.py delete mode 100644 tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py create mode 100644 tests/needle_samples/244ea203b20643bca72db45469423304fd037c3d306c8695a69af65a9f06a127.py create mode 100644 tests/needle_samples/27440126f646ce3b31cdb1e1d705c77c27c8adec0897398254cc98a2239bae15.py create mode 100644 tests/needle_samples/4d7d83e144a4a22bd50876f5b7f217433c142951288d31cf2b4ddd213d9653ff.py create mode 100644 tests/needle_samples/5a5575e32cb7b5b0c967414dd59b6f28d1bc7f72686c22fdc21bab81ca6a07ba.py create mode 100644 tests/needle_samples/73e618e87fa5f5006447fada779ff254a81eb20ae047547eadd2906537be4501.py create mode 100644 tests/needle_samples/7706c7c786b6a3630776ce0324c6f5104c074dbbffe5b4619cb39ab2662442be.py create mode 100644 tests/needle_samples/7ecce0caaa58c02e1c5bbd859ced9b448a792b9fb6459d399a551260c671a30a.py create mode 100644 tests/needle_samples/8d5136b2f1a84f7ec4feffea8928f74f2d289753d3c05ac1f4e830e1200c9227.py create mode 100644 tests/needle_samples/90c5e8e0ec3e3941083f8d0760844be6db23d43ac1484aa2b510cda10b4c955f.py create mode 100644 tests/needle_samples/98cc270385fc3af126f248be412bddf5b25170d21d4c028321c86adc54db5e85.py create mode 100644 tests/needle_samples/b5dcb6e09d7cb3de9324d772eb796a337a69626431d9c5c908cf9876bf110b18.py create mode 100644 tests/needle_samples/b94364dbc8bffa5fcc0597b79c6e14769729512141792f872c68d33742fb1a5b.py create mode 100644 tests/needle_samples/c25e4fd8c1855dc50edb9f957e32508cb51f0048de4f239942dd67f28b99faf4.py create mode 100644 tests/needle_samples/c425da7a8eb7d9f4215c7b275f083efdad310fee270b14b2469c6cc2d85be905.py create mode 100644 tests/needle_samples/c67b535d3c27cbbcad8d1363d5ca977d76388670ac7694316ac20fd05753a55b.py create mode 100644 tests/needle_samples/e6709adfa9f64750ce4c4eec0553901f0694b860dcf2e7c28cde53604e6b6a41.py create mode 100644 tests/needle_samples/ea336590273830ee68bf5f4f0d30cf7b7bb35580d4db6d1eec632e7a4611a69a.py create mode 100644 tests/needle_samples/ea4a0801511a58d00b147d4e2dec18a0f85ef85646f368a67be58f0bc96c3e78.py create mode 100644 tests/needle_samples/eded9f3c89e3941dce860b741fa2140d4951fb6d9d9a3cae6b1c48535a7c9ef6.py create mode 100644 tests/needle_samples/f2ed702db1ac25af50e63c53a2b9c19c9bb63a88a3bb81122cd9d542e9392cdb.py create mode 100644 tests/needle_samples/f696264940b83e4526d71bc10c48bb4663b15256b4ca9093f8f5ed8ee7c8a883.py create mode 100644 tests/needle_samples/fa9f618733d8cc220f6903802ea66feca2f0c33a75b3a25f7286ee7aa2df319d.py create mode 100644 tests/remove_one_samples/130ceec6b47d9f2352d57896b6a9acd218a9d80baf1d5f6ab91c8e5e50cc47f1.py create mode 100644 tests/remove_one_samples/17c6779d5589a8ebb44a17037e0d132bf2c36fa3d459738de9ea14d08f9cad7c.py create mode 100644 tests/remove_one_samples/7a9c7313257e72dbc14bc30a03d5495ce51609f353cf4b025627d4c8945f7bed.py create mode 100644 tests/remove_one_samples/a7309968ffb04fd78fc6873d46d01a017d891fe8cd377dd0cbf0771d7f283e85.py create mode 100644 tests/remove_one_samples/b51eb40ca6336b75936d070c9ed61d418acee4dd2e34a8c2ac9b5e3035a3c9b5.py create mode 100644 tests/remove_one_samples/dfddbb56eaec419884dd8e95ed62318ce0a96179f85e13c783d4195474db3605.py create mode 100644 tests/remove_one_samples/f12b11e61ce707620d2593ff9b367b945dcea01efca58e1b734f31b4613e6f69.py create mode 100644 tests/utils.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f7368b..34d968a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/.gitignore b/.gitignore index 5962087..41f02d3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ __pycache__ dist .python-version .coverage* +pysource_minimize_testing/ +.mutmut-cache diff --git a/noxfile.py b/noxfile.py index dd707aa..9de9184 100644 --- a/noxfile.py +++ b/noxfile.py @@ -20,7 +20,7 @@ def mypy(session): session.run("mypy", "pysource_minimize", "tests") -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11"]) +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) def test(session): session.run_always("poetry", "install", "--with=dev", external=True) session.env["COVERAGE_PROCESS_START"] = str( @@ -28,6 +28,7 @@ def test(session): ) session.env["TOP"] = str(Path(__file__).parent) args = [] if session.posargs else ["-n", "auto", "-v"] + session.install("attrs") # some bug with pytest-subtests session.run("pytest", *args, "tests", *session.posargs) diff --git a/poetry.lock b/poetry.lock index d63c76c..cbd425b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,20 +2,20 @@ [[package]] name = "asttokens" -version = "2.0.8" +version = "2.4.0" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ - {file = "asttokens-2.0.8-py2.py3-none-any.whl", hash = "sha256:e3305297c744ae53ffa032c45dc347286165e4ffce6875dc662b205db0623d86"}, - {file = "asttokens-2.0.8.tar.gz", hash = "sha256:c61e16246ecfb2cde2958406b4c8ebc043c9e6d73aaa83c941673b35e5d3a76b"}, + {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, + {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, ] [package.dependencies] -six = "*" +six = ">=1.12.0" [package.extras] -test = ["astroid (<=2.5.3)", "pytest"] +test = ["astroid", "pytest"] [[package]] name = "astunparse" @@ -32,23 +32,6 @@ files = [ six = ">=1.6.1,<2.0" wheel = ">=0.23.0,<1.0" -[[package]] -name = "attrs" -version = "22.1.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.5" -files = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] - -[package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] - [[package]] name = "click" version = "8.1.7" @@ -66,13 +49,13 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] @@ -165,29 +148,43 @@ files = [ [package.dependencies] coverage = "*" +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "execnet" -version = "1.9.0" +version = "2.0.2" description = "execnet: rapid multi-Python deployment" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, - {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, + {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, + {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, ] [package.extras] -testing = ["pre-commit"] +testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "importlib-metadata" -version = "5.1.0" +version = "6.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, - {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, ] [package.dependencies] @@ -195,61 +192,61 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "mypy" -version = "1.2.0" +version = "1.4.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" files = [ - {file = "mypy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d"}, - {file = "mypy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"}, - {file = "mypy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e"}, - {file = "mypy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a"}, - {file = "mypy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128"}, - {file = "mypy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41"}, - {file = "mypy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb"}, - {file = "mypy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937"}, - {file = "mypy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9"}, - {file = "mypy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602"}, - {file = "mypy-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140"}, - {file = "mypy-1.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336"}, - {file = "mypy-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e"}, - {file = "mypy-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119"}, - {file = "mypy-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a"}, - {file = "mypy-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950"}, - {file = "mypy-1.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6"}, - {file = "mypy-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5"}, - {file = "mypy-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8"}, - {file = "mypy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed"}, - {file = "mypy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f"}, - {file = "mypy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521"}, - {file = "mypy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238"}, - {file = "mypy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48"}, - {file = "mypy-1.2.0-py3-none-any.whl", hash = "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394"}, - {file = "mypy-1.2.0.tar.gz", hash = "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.10" +typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -270,27 +267,24 @@ files = [ [[package]] name = "packaging" -version = "21.3" +version = "23.2" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [package.dependencies] @@ -302,108 +296,82 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "psutil" -version = "5.9.4" +version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, - {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, - {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, - {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, - {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, - {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, - {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, - {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, ] [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - -[[package]] -name = "Pygments" -version = "2.13.0" +name = "pygments" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] plugins = ["importlib-metadata"] -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - [[package]] name = "pysource-codegen" -version = "0.3.0" +version = "0.4.1" description = "" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "pysource_codegen-0.3.0-py3-none-any.whl", hash = "sha256:434e77b81f570954958564d552ffedf7a0ce76bed2c3ca9beb260027a3b5f1ee"}, - {file = "pysource_codegen-0.3.0.tar.gz", hash = "sha256:69c2bca0416fdbb6b8e9b4e96bbf8ac11e37637f9f6a240bb563edc057ff7174"}, + {file = "pysource_codegen-0.4.1-py3-none-any.whl", hash = "sha256:0aa8e53c8886d09f002ad73119b0001e3227f437af8ffd3e7c8b42c41223a2a4"}, + {file = "pysource_codegen-0.4.1.tar.gz", hash = "sha256:0dbd666d87c1ccd377cfd5a6f66ead24f5da97f78f3ba1c6a899cde3533c25d1"}, ] [package.dependencies] -astunparse = ">=1.6.3,<2.0.0" +astunparse = {version = ">=1.6.3,<2.0.0", markers = "python_version < \"3.9\""} typed-ast = ">=1.5.5,<2.0.0" typing-extensions = ">=4.7.1,<5.0.0" [[package]] name = "pytest" -version = "7.1.3" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, - {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-subtests" @@ -421,13 +389,13 @@ pytest = ">=7.0" [[package]] name = "pytest-xdist" -version = "3.1.0" +version = "3.3.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-xdist-3.1.0.tar.gz", hash = "sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c"}, - {file = "pytest_xdist-3.1.0-py3-none-any.whl", hash = "sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89"}, + {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, + {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, ] [package.dependencies] @@ -544,34 +512,34 @@ files = [ [[package]] name = "wheel" -version = "0.38.4" +version = "0.41.2" description = "A built-package format for Python" optional = false python-versions = ">=3.7" files = [ - {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, - {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, + {file = "wheel-0.41.2-py3-none-any.whl", hash = "sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"}, + {file = "wheel-0.41.2.tar.gz", hash = "sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985"}, ] [package.extras] -test = ["pytest (>=3.0.0)"] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "zipp" -version = "3.11.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "c7213aa10a76a7f8e4855c8b2a5405aeddbfc04fe6b2befdbcb8c4e2ddeb06ea" +content-hash = "840b1effe8fecc04cce92bc4ead7f7dae1ca6f43b64f0514750aaf796f0a38ca" diff --git a/pyproject.toml b/pyproject.toml index 34389fa..aacc1c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ pytest-xdist = {extras = ["psutil"], version = "^3.1.0"} coverage-enable-subprocess = "^1.0" coverage = "^6.5.0" mypy = "^1.2.0" -pysource-codegen = "^0.3.0" +pysource-codegen = "^0.4.1" [tool.poetry.scripts] pysource-minimize = "pysource_minimize.__main__:main" @@ -36,6 +36,9 @@ parallel = true branch = true data_file = "$TOP/.coverage" +[tool.coverage.report] +exclude_lines = ["assert False", "raise NotImplemented"] + [tool.black] force-exclude = "tests/.*_samples" skip_magic_trailing_comma = true diff --git a/pysource_minimize/__init__.py b/pysource_minimize/__init__.py index 38f31b8..bbf83eb 100644 --- a/pysource_minimize/__init__.py +++ b/pysource_minimize/__init__.py @@ -1 +1,5 @@ from ._minimize import minimize +from ._minimize import StopMinimization + + +__all__ = ("minimize", "StopMinimization") diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index f18b072..8049b7c 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -1,16 +1,16 @@ import ast import copy -import os import sys from typing import List from typing import Union + try: from ast import unparse except ImportError: from astunparse import unparse # type: ignore -TESTING = "PYTEST_CURRENT_TEST" in os.environ +TESTING = False py311 = sys.version_info >= (3, 11) @@ -29,6 +29,40 @@ def is_block(nodes): ) +class StopMinimization(Exception): + pass + + +class CoverageRequired(Exception): + pass + + +def equal_ast(lhs, rhs): + if type(lhs) != type(rhs): + return False + + elif isinstance(lhs, list): + if len(lhs) != len(rhs): + return False + + return all(equal_ast(l, r) for l, r in zip(lhs, rhs)) + + elif isinstance(lhs, ast.AST): + return all( + equal_ast(getattr(lhs, field), getattr(rhs, field)) + for field in lhs._fields + if field not in ("ctx",) + ) + else: + return lhs == rhs + assert False, f"unexpected type {type(lhs)}" + + +def coverage_required(): + if TESTING: + raise CoverageRequired() + + def arguments( node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda] ) -> List[ast.arg]: @@ -42,23 +76,19 @@ def arguments( class Minimizer: - def __init__(self, source, checker, progress_callback): + def __init__(self, original_ast, checker, progress_callback): self.checker = checker self.progress_callback = progress_callback + self.stop = False - self.original_source = source - if sys.version_info >= (3, 8): - self.original_ast = ast.parse(source, type_comments=True) - else: - self.original_ast = ast.parse(source) - + # duplicate nodes like ast.Load() class UniqueObj(ast.NodeTransformer): def visit(self, node): if not node._fields: return type(node)() return super().visit(node) - self.original_ast = UniqueObj().visit(self.original_ast) + self.original_ast = UniqueObj().visit(original_ast) self.original_nodes_number = self.nodes_of(self.original_ast) @@ -67,18 +97,13 @@ def visit(self, node): self.replaced = {} - if not self.checker(self.original_source): - raise ValueError("checker return False: nothing to minimize here") - - source = self.get_source({}) - if not self.checker(source): - print("ast.unparse removes the error minimize can not help here") - self.source = self.original_source - return - - self.minimize_stmt(self.original_ast) + try: + if not self.checker(self.original_ast): + raise ValueError("checker return False: nothing to minimize here") - self.source = self.get_source(self.replaced) + self.minimize_stmt(self.original_ast) + except StopMinimization: + self.stop = True def get_ast(self, node, replaced={}): replaced = {**self.replaced, **replaced} @@ -155,17 +180,18 @@ def map_node(node): for child in ast.iter_child_nodes(node): map_node(child) + # TODO: this could be optimized (copy all, reduce) -> (generate new ast nodes) map_node(tmp_ast) return tmp_ast - def get_source_tree(self, replaced): + def get_current_node(self, ast_node): + return self.get_ast(ast_node) + + def get_current_tree(self, replaced): tree = self.get_ast(self.original_ast, replaced) ast.fix_missing_locations(tree) - return unparse(tree), tree - - def get_source(self, replaced): - return self.get_source_tree(replaced)[0] + return tree @staticmethod def nodes_of(tree): @@ -176,7 +202,13 @@ def try_with(self, replaced={}): returns True if the minimization was successfull """ - source, tree = self.get_source_tree(replaced) + if TESTING: + double_defined = self.replaced.keys() & replaced.keys() + assert ( + not double_defined + ), f"the keys {double_defined} are mapped a second time" + + tree = self.get_current_tree(replaced) for node in ast.walk(tree): if isinstance(node, ast.Delete) and any( @@ -187,24 +219,19 @@ def try_with(self, replaced={}): # delete None return False - if 0: - print() - print("replaced:", self.replaced, replaced) - if source.count("\n") < 15: - print(source) - - if 0: - try: - compile(source, "", "exec") - except Exception: - return False + valid_minimization = False - if self.checker(source): - self.replaced.update(replaced) - self.progress_callback(self.nodes_of(tree), self.original_nodes_number) - return True + try: + valid_minimization = self.checker(tree) + except StopMinimization: + valid_minimization = True + raise + finally: + if valid_minimization: + self.replaced.update(replaced) + self.progress_callback(self.nodes_of(tree), self.original_nodes_number) - return False + return valid_minimization def try_attr(self, node, attr_name, new_attr): return self.try_with({(node.__index, attr_name): new_attr}) @@ -250,12 +277,23 @@ def minimize(self, o): return self.minimize_expr(o) elif isinstance(o, list): return self.minimize_list(o, self.minimize) + elif isinstance(o, ast.arg): + return self.minimize_arg(o) else: raise TypeError(type(o)) + def minimize_arg(self, arg: ast.arg): + self.minimize_optional(arg.annotation) + + if sys.version_info >= (3, 8): + self.try_none(arg.type_comment) + def minimize_expr(self, node): if isinstance(node, ast.BoolOp): - self.minimize_list(node.values, self.minimize_expr, 1) + remaining = self.minimize_list(node.values, self.minimize_expr, 1) + if len(remaining) == 1: + self.try_only(node, remaining[0]) + elif isinstance(node, ast.Compare): if self.try_only(node, node.left): self.minimize(node.left) @@ -274,25 +312,32 @@ def minimize_expr(self, node): self.try_only_minimize(node, node.value, node.slice) elif isinstance(node, ast.FormattedValue): - if ( - isinstance(node.format_spec, ast.JoinedStr) - and len(node.format_spec.values) == 1 - and self.try_only(node, node.format_spec.values[0]) - ): - self.minimize(node.format_spec.values[0]) - return + if isinstance(node.format_spec, ast.JoinedStr): + # work around for https://github.com/python/cpython/issues/110309 + spec = [ + v + for v in node.format_spec.values + if not (isinstance(v, ast.Constant) and v.value == "") + ] + if len(spec) == 1 and self.try_only(node, spec[0]): + self.minimize(spec[0]) + return self.try_none(node.format_spec) self.minimize_expr(node.value) elif isinstance(node, ast.JoinedStr): - if ( - len(node.values) == 1 - and isinstance(node.values[0], ast.FormattedValue) - and self.try_only(node, node.values[0].value) - ): - self.minimize(node.values[0].value) - return + for v in node.values: + if isinstance(v, ast.FormattedValue) and self.try_only(node, v.value): + self.minimize(v.value) + return + if ( + isinstance(v, ast.FormattedValue) + and v.format_spec + and self.try_only(node, v.format_spec) + ): + self.minimize(v.format_spec) + return self.minimize(node.values) # todo minimize values @@ -300,7 +345,6 @@ def minimize_expr(self, node): elif isinstance(node, ast.Slice): self.try_only_minimize(node, node.lower, node.upper, node.step) elif isinstance(node, ast.Lambda): - if self.try_only_minimize(node, node.body): return @@ -323,25 +367,26 @@ def minimize_expr(self, node): elif isinstance(node, ast.YieldFrom): self.try_only_minimize(node, node.value) elif isinstance(node, ast.Dict): - for n in [*node.values, *node.keys]: - if self.try_only(node, n): - self.minimize(n) - return - - self.minimize_lists( + remaining = self.minimize_lists( (node.keys, node.values), (self.minimize, self.minimize) ) + if len(remaining) == 1: + if self.try_only(node, remaining[0][0]): + return + + if self.try_only(node, remaining[0][1]): + return elif isinstance(node, (ast.Set)): - if node.elts and self.try_only(node, node.elts[0]): - self.minimize(node.elts[0]) - return - self.minimize_list(node.elts, self.minimize, 1) + remaining = self.minimize_list(node.elts, self.minimize, 1) + # TODO: min size 1? + if len(remaining) == 1: + self.try_only(node, remaining[0]) elif isinstance(node, (ast.List, ast.Tuple)): - if node.elts and self.try_only(node, node.elts[0]): - self.minimize(node.elts[0]) - return - self.minimize(node.elts) + remaining = self.minimize_list(node.elts, self.minimize, 1) + # TODO: min size 1? + if len(remaining) == 1: + self.try_only(node, remaining[0]) elif isinstance(node, ast.Name): pass elif isinstance(node, ast.Constant): @@ -401,7 +446,7 @@ def minimize_expr(self, node): elif isinstance(node, ast.NamedExpr): self.try_only_minimize(node, node.target, node.value) else: - raise TypeError(node) # Expr + assert False, "expression is not handled " % (node) def minimize_optional(self, node): if node is not None and not self.try_none(node): @@ -460,29 +505,17 @@ def minimize_args_of(self, func): self.minimize(child) return True - def minimize_arg(arg: ast.arg): - if arg.annotation is not None and not self.try_none(arg.annotation): - self.minimize(arg.annotation) - - if sys.version_info >= (3, 8): - self.try_none(arg.type_comment) - if py38: - self.minimize_list(args.posonlyargs, minimize_arg) - - self.minimize_list(args.defaults, self.minimize) + self.minimize_list(args.posonlyargs) - self.minimize_list(args.args, minimize_arg) + self.minimize_list(args.defaults) - self.minimize_lists( - (args.kwonlyargs, args.kw_defaults), (minimize_arg, self.minimize) - ) + self.minimize_list(args.args) - if args.vararg is not None and not self.try_none(args.vararg): - minimize_arg(args.vararg) + self.minimize_lists((args.kwonlyargs, args.kw_defaults)) - if args.kwarg is not None and not self.try_none(args.kwarg): - minimize_arg(args.kwarg) + self.minimize_optional(args.vararg) + self.minimize_optional(args.kwarg) return False @@ -513,7 +546,16 @@ def minimize_stmt(self, node): if self.minimize_args_of(node): return + if sys.version_info >= (3, 12): + for p in node.type_params: + if ( + isinstance(p, ast.TypeVar) + and p.bound is not None + and self.try_only(node, p.bound) + ): + self.minimize(p.bound) + return self.minimize_list(node.type_params, self.minimize_type_param) if node.returns: @@ -528,6 +570,15 @@ def minimize_stmt(self, node): return if sys.version_info >= (3, 12): + for p in node.type_params: + if ( + isinstance(p, ast.TypeVar) + and p.bound is not None + and self.try_only(node, p.bound) + ): + self.minimize(p.bound) + return + coverage_required() self.minimize_list(node.type_params, self.minimize_type_param) for e in [ @@ -539,8 +590,10 @@ def minimize_stmt(self, node): ]: if self.try_only(node, e): self.minimize(e) + coverage_required() return + coverage_required() self.minimize(node.bases) self.minimize_list( node.keywords, terminal=lambda kw: self.minimize(kw.value) @@ -548,12 +601,14 @@ def minimize_stmt(self, node): return elif isinstance(node, ast.Return): + coverage_required() self.try_only_minimize(node, node.value) elif isinstance(node, ast.Delete): - if len(node.targets) == 1 and self.try_only(node, node.targets[0]): - self.minimize(node.targets[0]) - return + for t in node.targets: + if self.try_only(node, t): + self.minimize(t) + return self.minimize_list(node.targets, self.minimize, 1) @@ -564,23 +619,33 @@ def minimize_stmt(self, node): self.try_only_minimize(node, node.target, node.value) elif isinstance(node, ast.AnnAssign): + for child in [node.target, node.value, node.annotation]: + if child is not None and self.try_only(node, child): + self.minimize(child) + return - if node.value is None or not self.try_node( + coverage_required() + if not self.try_node( node, ast.Assign(targets=[node.target], value=node.value) ): - self.try_only_minimize(node, node.target, node.value, node.annotation) + coverage_required() + self.minimize(node.target) + self.minimize(node.value) + self.minimize(node.annotation) elif isinstance(node, (ast.For, ast.AsyncFor)): if self.try_only(node, node.target): self.minimize(node.target) return + coverage_required() self.minimize_list(node.body, self.minimize_stmt) body = self.get_ast(node) if not any( isinstance(n, (ast.Break, ast.Continue)) for n in ast.walk(body) ): if self.try_only(node, node.body): + coverage_required() return self.try_only_minimize(node, node.iter, node.orelse) @@ -593,6 +658,7 @@ def minimize_stmt(self, node): isinstance(n, (ast.Break, ast.Continue)) for n in ast.walk(body) ): if self.try_only(node, node.body): + coverage_required() return self.try_only_minimize(node, node.test, node.orelse) @@ -601,16 +667,17 @@ def minimize_stmt(self, node): pass elif isinstance(node, ast.If): + coverage_required() self.try_only_minimize(node, node.test, node.body, node.orelse) elif isinstance(node, (ast.With, ast.AsyncWith)): - if self.try_only_minimize(node, node.body): return for item in node.items: if self.try_only(node, item.context_expr): self.minimize(item.context_expr) + coverage_required() return if item.optional_vars is not None and self.try_only( @@ -632,6 +699,7 @@ def minimize_item(item: ast.withitem): for case_ in node.cases: for e in [case_.guard, case_.body]: if e is not None and self.try_only(node, e): + coverage_required() self.minimize(e) return @@ -643,47 +711,46 @@ def minimize_item(item: ast.withitem): self.minimize_list(node.cases, self.minimize_match_case, 1) elif isinstance(node, ast.Raise): - if not self.try_only(node, node.exc) and not self.try_only(node, node.exc): + if self.try_only(node, node.exc): + self.minimize(node.exc) + return + + if node.cause and not self.try_only(node, node.cause): self.minimize_optional(node.cause) # cause requires exc # `raise from cause` is not valid if self.get_ast(node).cause: + coverage_required() self.minimize(node.exc) else: + coverage_required() self.minimize_optional(node.exc) elif isinstance(node, ast.Try) or (py311 and isinstance(node, ast.TryStar)): try_star = py311 and isinstance(node, ast.TryStar) - if try_star and self.try_node( - node, - ast.Try( - body=node.body, - handlers=node.handlers, - orelse=node.orelse, - finalbody=node.finalbody, - ), - ): - return - if self.try_only(node, node.body): self.minimize(node.body) return if node.orelse and self.try_only(node, node.orelse): + coverage_required() self.minimize(node.orelse) return if node.finalbody and self.try_only(node, node.finalbody): + coverage_required() self.minimize(node.finalbody) return for h in node.handlers: if self.try_only(node, h.body): self.minimize(h.body) + coverage_required() return if h.type is not None and self.try_only(node, h.type): self.minimize(h.type) + coverage_required() return def minimize_except_handler(handler): @@ -692,17 +759,35 @@ def minimize_except_handler(handler): if not handler.name and not try_star: self.minimize_optional(handler.type) + if handler.name: + self.try_attr(handler, "name", None) + self.minimize_list( node.handlers, minimize_except_handler, 1 # 0 if node.finalbody else 1 ) + coverage_required() self.minimize(node.body) self.minimize(node.orelse) self.minimize(node.finalbody) + if try_star and self.try_node( + node, + ast.Try( + body=node.body, + handlers=node.handlers, + orelse=node.orelse, + finalbody=node.finalbody, + ), + ): + return + elif isinstance(node, ast.Assert): if node.msg: + if self.try_only(node, node.msg): + self.minimize(node.msg) + return + if not self.try_none(node.msg): - self.try_only(node, node.msg) self.minimize(node.msg) if self.try_only_minimize(node, node.test): @@ -722,9 +807,17 @@ def minimize_except_handler(handler): elif isinstance(node, ast.Module): self.minimize(node.body) elif sys.version_info >= (3, 12) and isinstance(node, ast.TypeAlias): + for p in node.type_params: + if ( + isinstance(p, ast.TypeVar) + and p.bound is not None + and self.try_only(node, p.bound) + ): + self.minimize(p.bound) + return + if self.try_only_minimize(node, node.name, node.value): + return self.minimize_list(node.type_params, self.minimize_type_param) - self.minimize(node.value) - self.minimize(node.name) elif isinstance(node, ast.Pass): pass @@ -736,14 +829,17 @@ def minimize_type_param(self, node): if isinstance(node, ast.TypeVar): self.minimize_optional(node.bound) - def minimize_lists(self, lists, terminals, minimal=0): + def minimize_lists(self, lists, terminals=None, minimal=0): + if terminals is None: + terminals = [self.minimize for _ in lists] + lists = list(zip(*lists)) max_remove = len(lists) - minimal import itertools def try_without(l): - self.try_without(itertools.chain.from_iterable(l)) + return self.try_without(itertools.chain.from_iterable(l)) def wo(l): nonlocal max_remove @@ -778,8 +874,15 @@ def devide(l): for terminal, node in zip(terminals, nodes): terminal(node) - def minimize_list(self, stmts, terminal, minimal=0): - # return self.minimize_lists((stmts,),(terminal,),minimal=0) + return remaining + + def minimize_list(self, stmts, terminal=None, minimal=0): + if terminal is None: + terminal = self.minimize + + # result= self.minimize_lists((stmts,),(terminal,),minimal=0) + # return [e[0] for e in result] + stmts = list(stmts) max_remove = len(stmts) - minimal @@ -815,9 +918,59 @@ def devide(l): for node in remaining: terminal(node) + return remaining + + +def minimize_ast( + original_ast: ast.AST, + checker, + *, + progress_callback=lambda current, total: None, + retries=1, + dbg=False, +) -> ast.AST: + """ + minimzes the AST + + Args: + ast: the ast to minimize + checker: a function which gets the ast and returns `True` when the criteria is fullfilled. + progress_callback: function which is called everytime the ast gets a bit smaller. + retries: the number of retries which sould be performed when the ast could be minimized (useful for non deterministic issues) + + returns the minimized ast + """ + + last_success = 0 + + dbg = True + + current_ast = original_ast + while last_success <= retries: + minimizer = Minimizer(current_ast, checker, progress_callback) + new_ast = minimizer.get_current_tree({}) + + minimized_something = not equal_ast(new_ast, current_ast) + + current_ast = new_ast + + if minimizer.stop: + break + if minimized_something: + last_success = 0 + else: + last_success += 1 + + return current_ast + def minimize( - source: str, checker, *, progress_callback=lambda current, total: None + source: str, + checker, + *, + progress_callback=lambda current, total: None, + retries=1, + dbg=False, ) -> str: """ minimzes the source code @@ -826,14 +979,33 @@ def minimize( source: the source code to minimize checker: a function which gets the source and returns `True` when the criteria is fullfilled. progress_callback: function which is called everytime the source gets a bit smaller. + retries: the number of retries which sould be performed when the ast could be minimized (useful for non deterministic issues) returns the minimized source """ + if sys.version_info >= (3, 8): + original_ast = ast.parse(source, type_comments=True) + else: + original_ast = ast.parse(source) + + def source_checker(new_ast): + source = unparse(new_ast) + try: + compile(source, "", "exec") + except: + return False + + return checker(source) + + if not source_checker(original_ast): + raise ValueError("ast.unparse removes the error minimize can not help here") + + minimized_ast = minimize_ast( + original_ast, + source_checker, + progress_callback=progress_callback, + retries=retries, + dbg=dbg, + ) - last_result = "" - - while last_result != source: - minimizer = Minimizer(source, checker, progress_callback) - last_result, source = source, minimizer.source - - return last_result + return unparse(minimized_ast) diff --git a/tests/conftest.py b/tests/conftest.py index fe516c1..841eb7f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,10 @@ +import os import random import pytest +os.environ["PYSOURCE_TESTING"] = "1" + @pytest.fixture(params=range(0)) def seed(): @@ -17,9 +20,44 @@ def pytest_addoption(parser, pluginmanager): def pytest_sessionfinish(session, exitstatus): + print("exitstatus", exitstatus) + if exitstatus == 0 and session.config.option.generate_samples: + from .test_remove_one import generate_remove_one from .test_needle import generate_needle - generate_needle() + for i in range(2): + generate_remove_one() + generate_needle() # teardown_stuff + + +from contextlib import contextmanager + +depth = 0 + + +@contextmanager +def ctx(msg): + global depth + print("│" * depth + "┌", msg) + depth += 1 + try: + yield + finally: + depth -= 1 + print("│" * depth + "└", msg) + + +import textwrap + + +def ctx_print(*a): + s = " ".join(str(e) for e in a) + prefix = "│" * (depth) + print(textwrap.indent(s, prefix, lambda line: True)) + + +__builtins__["ctx"] = ctx +__builtins__["ctx_print"] = ctx_print diff --git a/tests/needle_samples/09e9e63c9378d4dc4985901994174f67f66594f9d621a709b0ad7fd5b7dad6a7.py b/tests/needle_samples/09e9e63c9378d4dc4985901994174f67f66594f9d621a709b0ad7fd5b7dad6a7.py new file mode 100644 index 0000000..4d2748b --- /dev/null +++ b/tests/needle_samples/09e9e63c9378d4dc4985901994174f67f66594f9d621a709b0ad7fd5b7dad6a7.py @@ -0,0 +1,2 @@ +def name_5[name_5: needle_17597](): + pass \ No newline at end of file diff --git a/tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py b/tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py deleted file mode 100644 index a8b6ee3..0000000 --- a/tests/needle_samples/0c5dc4b2ba515d549d7a49dc43138a6db157ad30c60ab250811e0e3ced2b356b.py +++ /dev/null @@ -1 +0,0 @@ -lambda *, name_5=needle_17597: name_5 \ No newline at end of file diff --git a/tests/needle_samples/244ea203b20643bca72db45469423304fd037c3d306c8695a69af65a9f06a127.py b/tests/needle_samples/244ea203b20643bca72db45469423304fd037c3d306c8695a69af65a9f06a127.py new file mode 100644 index 0000000..d37f9f3 --- /dev/null +++ b/tests/needle_samples/244ea203b20643bca72db45469423304fd037c3d306c8695a69af65a9f06a127.py @@ -0,0 +1 @@ +del something[{needle_17597 for name_0 in name_0}], something \ No newline at end of file diff --git a/tests/needle_samples/27440126f646ce3b31cdb1e1d705c77c27c8adec0897398254cc98a2239bae15.py b/tests/needle_samples/27440126f646ce3b31cdb1e1d705c77c27c8adec0897398254cc98a2239bae15.py new file mode 100644 index 0000000..71308eb --- /dev/null +++ b/tests/needle_samples/27440126f646ce3b31cdb1e1d705c77c27c8adec0897398254cc98a2239bae15.py @@ -0,0 +1,2 @@ + +(name_3, needle_17597) diff --git a/tests/needle_samples/4d7d83e144a4a22bd50876f5b7f217433c142951288d31cf2b4ddd213d9653ff.py b/tests/needle_samples/4d7d83e144a4a22bd50876f5b7f217433c142951288d31cf2b4ddd213d9653ff.py new file mode 100644 index 0000000..c9ace76 --- /dev/null +++ b/tests/needle_samples/4d7d83e144a4a22bd50876f5b7f217433c142951288d31cf2b4ddd213d9653ff.py @@ -0,0 +1,2 @@ + +raise name_2 from needle_17597 diff --git a/tests/needle_samples/5a5575e32cb7b5b0c967414dd59b6f28d1bc7f72686c22fdc21bab81ca6a07ba.py b/tests/needle_samples/5a5575e32cb7b5b0c967414dd59b6f28d1bc7f72686c22fdc21bab81ca6a07ba.py new file mode 100644 index 0000000..03bb46f --- /dev/null +++ b/tests/needle_samples/5a5575e32cb7b5b0c967414dd59b6f28d1bc7f72686c22fdc21bab81ca6a07ba.py @@ -0,0 +1,2 @@ + +(needle_17597 or name_3) diff --git a/tests/needle_samples/73e618e87fa5f5006447fada779ff254a81eb20ae047547eadd2906537be4501.py b/tests/needle_samples/73e618e87fa5f5006447fada779ff254a81eb20ae047547eadd2906537be4501.py new file mode 100644 index 0000000..3f81bec --- /dev/null +++ b/tests/needle_samples/73e618e87fa5f5006447fada779ff254a81eb20ae047547eadd2906537be4501.py @@ -0,0 +1,2 @@ + +{name_5 for name_3 in needle_17597} diff --git a/tests/needle_samples/7706c7c786b6a3630776ce0324c6f5104c074dbbffe5b4619cb39ab2662442be.py b/tests/needle_samples/7706c7c786b6a3630776ce0324c6f5104c074dbbffe5b4619cb39ab2662442be.py new file mode 100644 index 0000000..051ff37 --- /dev/null +++ b/tests/needle_samples/7706c7c786b6a3630776ce0324c6f5104c074dbbffe5b4619cb39ab2662442be.py @@ -0,0 +1 @@ +lambda *, name_5=needle_17597: name_2 \ No newline at end of file diff --git a/tests/needle_samples/7ecce0caaa58c02e1c5bbd859ced9b448a792b9fb6459d399a551260c671a30a.py b/tests/needle_samples/7ecce0caaa58c02e1c5bbd859ced9b448a792b9fb6459d399a551260c671a30a.py new file mode 100644 index 0000000..7ee19d5 --- /dev/null +++ b/tests/needle_samples/7ecce0caaa58c02e1c5bbd859ced9b448a792b9fb6459d399a551260c671a30a.py @@ -0,0 +1 @@ +lambda: needle_17597 \ No newline at end of file diff --git a/tests/needle_samples/8d5136b2f1a84f7ec4feffea8928f74f2d289753d3c05ac1f4e830e1200c9227.py b/tests/needle_samples/8d5136b2f1a84f7ec4feffea8928f74f2d289753d3c05ac1f4e830e1200c9227.py new file mode 100644 index 0000000..8a8ea53 --- /dev/null +++ b/tests/needle_samples/8d5136b2f1a84f7ec4feffea8928f74f2d289753d3c05ac1f4e830e1200c9227.py @@ -0,0 +1,4 @@ + + +def name_4(): + (yield from needle_17597) diff --git a/tests/needle_samples/90c5e8e0ec3e3941083f8d0760844be6db23d43ac1484aa2b510cda10b4c955f.py b/tests/needle_samples/90c5e8e0ec3e3941083f8d0760844be6db23d43ac1484aa2b510cda10b4c955f.py new file mode 100644 index 0000000..37e4e00 --- /dev/null +++ b/tests/needle_samples/90c5e8e0ec3e3941083f8d0760844be6db23d43ac1484aa2b510cda10b4c955f.py @@ -0,0 +1,2 @@ + +{*needle_17597} diff --git a/tests/needle_samples/98cc270385fc3af126f248be412bddf5b25170d21d4c028321c86adc54db5e85.py b/tests/needle_samples/98cc270385fc3af126f248be412bddf5b25170d21d4c028321c86adc54db5e85.py new file mode 100644 index 0000000..ceb8340 --- /dev/null +++ b/tests/needle_samples/98cc270385fc3af126f248be412bddf5b25170d21d4c028321c86adc54db5e85.py @@ -0,0 +1,2 @@ + +assert name_2, needle_17597 diff --git a/tests/needle_samples/b5dcb6e09d7cb3de9324d772eb796a337a69626431d9c5c908cf9876bf110b18.py b/tests/needle_samples/b5dcb6e09d7cb3de9324d772eb796a337a69626431d9c5c908cf9876bf110b18.py new file mode 100644 index 0000000..cbe3102 --- /dev/null +++ b/tests/needle_samples/b5dcb6e09d7cb3de9324d772eb796a337a69626431d9c5c908cf9876bf110b18.py @@ -0,0 +1,4 @@ + + +async def name_0(*, name_1=name_4[name_3]): + needle_17597: name_1 = {name_0 async for name_4 in name_2} diff --git a/tests/needle_samples/b94364dbc8bffa5fcc0597b79c6e14769729512141792f872c68d33742fb1a5b.py b/tests/needle_samples/b94364dbc8bffa5fcc0597b79c6e14769729512141792f872c68d33742fb1a5b.py new file mode 100644 index 0000000..3839091 --- /dev/null +++ b/tests/needle_samples/b94364dbc8bffa5fcc0597b79c6e14769729512141792f872c68d33742fb1a5b.py @@ -0,0 +1,2 @@ + +{name_5: needle_17597} diff --git a/tests/needle_samples/c25e4fd8c1855dc50edb9f957e32508cb51f0048de4f239942dd67f28b99faf4.py b/tests/needle_samples/c25e4fd8c1855dc50edb9f957e32508cb51f0048de4f239942dd67f28b99faf4.py new file mode 100644 index 0000000..6c45c87 --- /dev/null +++ b/tests/needle_samples/c25e4fd8c1855dc50edb9f957e32508cb51f0048de4f239942dd67f28b99faf4.py @@ -0,0 +1 @@ +raise needle_17597 from name_2 \ No newline at end of file diff --git a/tests/needle_samples/c425da7a8eb7d9f4215c7b275f083efdad310fee270b14b2469c6cc2d85be905.py b/tests/needle_samples/c425da7a8eb7d9f4215c7b275f083efdad310fee270b14b2469c6cc2d85be905.py new file mode 100644 index 0000000..c54f2f4 --- /dev/null +++ b/tests/needle_samples/c425da7a8eb7d9f4215c7b275f083efdad310fee270b14b2469c6cc2d85be905.py @@ -0,0 +1 @@ +{needle_17597: name_5 for name_0 in name_2 if name_2} \ No newline at end of file diff --git a/tests/needle_samples/c67b535d3c27cbbcad8d1363d5ca977d76388670ac7694316ac20fd05753a55b.py b/tests/needle_samples/c67b535d3c27cbbcad8d1363d5ca977d76388670ac7694316ac20fd05753a55b.py new file mode 100644 index 0000000..d8f4d8c --- /dev/null +++ b/tests/needle_samples/c67b535d3c27cbbcad8d1363d5ca977d76388670ac7694316ac20fd05753a55b.py @@ -0,0 +1 @@ +type needle_17597 = name_5 \ No newline at end of file diff --git a/tests/needle_samples/e6709adfa9f64750ce4c4eec0553901f0694b860dcf2e7c28cde53604e6b6a41.py b/tests/needle_samples/e6709adfa9f64750ce4c4eec0553901f0694b860dcf2e7c28cde53604e6b6a41.py new file mode 100644 index 0000000..f36bf52 --- /dev/null +++ b/tests/needle_samples/e6709adfa9f64750ce4c4eec0553901f0694b860dcf2e7c28cde53604e6b6a41.py @@ -0,0 +1 @@ +raise [needle_17597 for name_1 in name_5 if name_2] \ No newline at end of file diff --git a/tests/needle_samples/ea336590273830ee68bf5f4f0d30cf7b7bb35580d4db6d1eec632e7a4611a69a.py b/tests/needle_samples/ea336590273830ee68bf5f4f0d30cf7b7bb35580d4db6d1eec632e7a4611a69a.py new file mode 100644 index 0000000..e71f484 --- /dev/null +++ b/tests/needle_samples/ea336590273830ee68bf5f4f0d30cf7b7bb35580d4db6d1eec632e7a4611a69a.py @@ -0,0 +1,2 @@ +class name_3[name_5: needle_17597]: + pass \ No newline at end of file diff --git a/tests/needle_samples/ea4a0801511a58d00b147d4e2dec18a0f85ef85646f368a67be58f0bc96c3e78.py b/tests/needle_samples/ea4a0801511a58d00b147d4e2dec18a0f85ef85646f368a67be58f0bc96c3e78.py new file mode 100644 index 0000000..d5df6a2 --- /dev/null +++ b/tests/needle_samples/ea4a0801511a58d00b147d4e2dec18a0f85ef85646f368a67be58f0bc96c3e78.py @@ -0,0 +1,2 @@ + +(- needle_17597)() diff --git a/tests/needle_samples/eded9f3c89e3941dce860b741fa2140d4951fb6d9d9a3cae6b1c48535a7c9ef6.py b/tests/needle_samples/eded9f3c89e3941dce860b741fa2140d4951fb6d9d9a3cae6b1c48535a7c9ef6.py new file mode 100644 index 0000000..d6351bf --- /dev/null +++ b/tests/needle_samples/eded9f3c89e3941dce860b741fa2140d4951fb6d9d9a3cae6b1c48535a7c9ef6.py @@ -0,0 +1,4 @@ + + +async def name_3(): + (yield needle_17597) diff --git a/tests/needle_samples/f2ed702db1ac25af50e63c53a2b9c19c9bb63a88a3bb81122cd9d542e9392cdb.py b/tests/needle_samples/f2ed702db1ac25af50e63c53a2b9c19c9bb63a88a3bb81122cd9d542e9392cdb.py new file mode 100644 index 0000000..0bdc9de --- /dev/null +++ b/tests/needle_samples/f2ed702db1ac25af50e63c53a2b9c19c9bb63a88a3bb81122cd9d542e9392cdb.py @@ -0,0 +1 @@ +type name_5[name_2: needle_17597] = name_4 \ No newline at end of file diff --git a/tests/needle_samples/f696264940b83e4526d71bc10c48bb4663b15256b4ca9093f8f5ed8ee7c8a883.py b/tests/needle_samples/f696264940b83e4526d71bc10c48bb4663b15256b4ca9093f8f5ed8ee7c8a883.py new file mode 100644 index 0000000..7b1f4dc --- /dev/null +++ b/tests/needle_samples/f696264940b83e4526d71bc10c48bb4663b15256b4ca9093f8f5ed8ee7c8a883.py @@ -0,0 +1,4 @@ +try: + needle_17597 +except* {name_3 for name_2 in name_3 if name_3}: + pass \ No newline at end of file diff --git a/tests/needle_samples/fa9f618733d8cc220f6903802ea66feca2f0c33a75b3a25f7286ee7aa2df319d.py b/tests/needle_samples/fa9f618733d8cc220f6903802ea66feca2f0c33a75b3a25f7286ee7aa2df319d.py new file mode 100644 index 0000000..5f329da --- /dev/null +++ b/tests/needle_samples/fa9f618733d8cc220f6903802ea66feca2f0c33a75b3a25f7286ee7aa2df319d.py @@ -0,0 +1,2 @@ + +{needle_17597.name_5: name_3} diff --git a/tests/remove_one_samples/130ceec6b47d9f2352d57896b6a9acd218a9d80baf1d5f6ab91c8e5e50cc47f1.py b/tests/remove_one_samples/130ceec6b47d9f2352d57896b6a9acd218a9d80baf1d5f6ab91c8e5e50cc47f1.py new file mode 100644 index 0000000..8e74078 --- /dev/null +++ b/tests/remove_one_samples/130ceec6b47d9f2352d57896b6a9acd218a9d80baf1d5f6ab91c8e5e50cc47f1.py @@ -0,0 +1 @@ +type name_0 = name_3 \ No newline at end of file diff --git a/tests/remove_one_samples/17c6779d5589a8ebb44a17037e0d132bf2c36fa3d459738de9ea14d08f9cad7c.py b/tests/remove_one_samples/17c6779d5589a8ebb44a17037e0d132bf2c36fa3d459738de9ea14d08f9cad7c.py new file mode 100644 index 0000000..efd77e3 --- /dev/null +++ b/tests/remove_one_samples/17c6779d5589a8ebb44a17037e0d132bf2c36fa3d459738de9ea14d08f9cad7c.py @@ -0,0 +1,5 @@ +match name_5: + case name_4(): + name_5 + case name_1.name_3: + name_4 \ No newline at end of file diff --git a/tests/remove_one_samples/7a9c7313257e72dbc14bc30a03d5495ce51609f353cf4b025627d4c8945f7bed.py b/tests/remove_one_samples/7a9c7313257e72dbc14bc30a03d5495ce51609f353cf4b025627d4c8945f7bed.py new file mode 100644 index 0000000..030bc17 --- /dev/null +++ b/tests/remove_one_samples/7a9c7313257e72dbc14bc30a03d5495ce51609f353cf4b025627d4c8945f7bed.py @@ -0,0 +1 @@ +assert name_0, (name_5,) \ No newline at end of file diff --git a/tests/remove_one_samples/a7309968ffb04fd78fc6873d46d01a017d891fe8cd377dd0cbf0771d7f283e85.py b/tests/remove_one_samples/a7309968ffb04fd78fc6873d46d01a017d891fe8cd377dd0cbf0771d7f283e85.py new file mode 100644 index 0000000..3485ed9 --- /dev/null +++ b/tests/remove_one_samples/a7309968ffb04fd78fc6873d46d01a017d891fe8cd377dd0cbf0771d7f283e85.py @@ -0,0 +1,2 @@ +while 1: + pass \ No newline at end of file diff --git a/tests/remove_one_samples/b51eb40ca6336b75936d070c9ed61d418acee4dd2e34a8c2ac9b5e3035a3c9b5.py b/tests/remove_one_samples/b51eb40ca6336b75936d070c9ed61d418acee4dd2e34a8c2ac9b5e3035a3c9b5.py new file mode 100644 index 0000000..5f3bd18 --- /dev/null +++ b/tests/remove_one_samples/b51eb40ca6336b75936d070c9ed61d418acee4dd2e34a8c2ac9b5e3035a3c9b5.py @@ -0,0 +1,6 @@ +try: + pass +except name_2 if name_4 else name_2 as name_0: + pass +except {name_3 for name_3 in name_0 if name_4} as name_1: + pass \ No newline at end of file diff --git a/tests/remove_one_samples/dfddbb56eaec419884dd8e95ed62318ce0a96179f85e13c783d4195474db3605.py b/tests/remove_one_samples/dfddbb56eaec419884dd8e95ed62318ce0a96179f85e13c783d4195474db3605.py new file mode 100644 index 0000000..8436747 --- /dev/null +++ b/tests/remove_one_samples/dfddbb56eaec419884dd8e95ed62318ce0a96179f85e13c783d4195474db3605.py @@ -0,0 +1,2 @@ + +name_0 = name_0 diff --git a/tests/remove_one_samples/f12b11e61ce707620d2593ff9b367b945dcea01efca58e1b734f31b4613e6f69.py b/tests/remove_one_samples/f12b11e61ce707620d2593ff9b367b945dcea01efca58e1b734f31b4613e6f69.py new file mode 100644 index 0000000..ccc7a57 --- /dev/null +++ b/tests/remove_one_samples/f12b11e61ce707620d2593ff9b367b945dcea01efca58e1b734f31b4613e6f69.py @@ -0,0 +1,4 @@ + + +async def name_3(): + (await name_4) diff --git a/tests/test_needle.py b/tests/test_needle.py index 325ce72..2eb8ea6 100644 --- a/tests/test_needle.py +++ b/tests/test_needle.py @@ -2,12 +2,25 @@ import hashlib import itertools import random +import sys from pathlib import Path +from typing import Any import pytest from pysource_codegen import generate +import pysource_minimize._minimize +from .utils import testing_enabled + +try: + import pysource_minimize_testing # type: ignore +except ImportError: + import pysource_minimize as pysource_minimize_testing + + from pysource_minimize import minimize +from pysource_minimize._minimize import unparse + sample_dir = Path(__file__).parent / "needle_samples" @@ -17,6 +30,10 @@ def contains_one_needle(source): + try: + compile(source, "", "exec") + except: + return False return needle_count(source) == 1 @@ -31,7 +48,11 @@ def needle_count(source): def try_find_needle(source): assert contains_one_needle(source) - new_source = minimize(source, contains_one_needle) + with testing_enabled(): + new_source = pysource_minimize_testing.minimize( + source, contains_one_needle, retries=0 + ) + assert new_source.strip() == needle_name @@ -46,7 +67,14 @@ def test_needle(file): except: pytest.skip() + print(f"the following code can not be minimized to needle:") print(source) + + if sys.version_info >= (3, 9): + print() + print("ast:") + print(ast.dump(ast.parse(source), indent=2)) + try_find_needle(source) @@ -67,9 +95,22 @@ def generic_visit(self, node: ast.AST) -> ast.AST: return super().generic_visit(node) + if sys.version_info >= (3, 10): + + def visit_Match(self, node: ast.Match) -> Any: + node.subject = self.visit(node.subject) + for case_ in node.cases: + case_.body = [self.visit(b) for b in case_.body] + + return node + + +import sys + def generate_needle(): seed = random.randrange(0, 100000000) + print("seed:", seed) source = generate(seed, node_limit=10000, depth_limit=6) @@ -83,13 +124,17 @@ def generate_needle(): break try: - needle_source = ast.unparse(needle_tree) + needle_source = unparse(needle_tree) compile(needle_source, "", "exec") except: print("skip this needle") continue - assert contains_one_needle(needle_source) + if not contains_one_needle(needle_source): + # match 0: + # case needle: + # could be generated which can not be reduced to needle + continue try: try_find_needle(needle_source) @@ -98,26 +143,25 @@ def generate_needle(): print("minimize") def checker(source): + try: + compile(source, "", "exec") + except: + return False + if needle_count(source) != 1: return False try: try_find_needle(source) except: return True + return False - try: - new_source = minimize(needle_source, checker) - print(new_source) - ( - sample_dir - / f"{hashlib.sha256(new_source.encode('utf-8')).hexdigest()}.py" - ).write_text(new_source) - except: - print("minimize failed") - ( - sample_dir - / f"{hashlib.sha256(needle_source.encode('utf-8')).hexdigest()}.py" - ).write_text(source) - - raise + new_source = minimize(needle_source, checker) + print(new_source) + ( + sample_dir + / f"{hashlib.sha256(new_source.encode('utf-8')).hexdigest()}.py" + ).write_text(new_source) + + raise ValueError("new sample found") diff --git a/tests/test_remove_one.py b/tests/test_remove_one.py index 9e4cc76..5400786 100644 --- a/tests/test_remove_one.py +++ b/tests/test_remove_one.py @@ -1,12 +1,20 @@ import ast import hashlib +import random import sys from pathlib import Path import pytest from pysource_codegen import generate +import pysource_minimize._minimize from pysource_minimize import minimize +from tests.utils import testing_enabled + +try: + import pysource_minimize_testing # type: ignore +except ImportError: + import pysource_minimize as pysource_minimize_testing sample_dir = Path(__file__).parent / "remove_one_samples" @@ -18,6 +26,7 @@ def node_weights(source): def weight(node): + result = 1 if isinstance( node, ( @@ -43,56 +52,67 @@ def weight(node): ast.ImportFrom, ), ): - return 0 + result = 0 if isinstance(node, (ast.GeneratorExp, ast.ListComp, ast.SetComp)): - return 0 + result = 0 if isinstance(node, (ast.DictComp)): - return -2 + result = -2 if isinstance(node, ast.comprehension): # removing comrehension removes variable and iterable - return -1 + result = -1 if isinstance(node, (ast.Dict)): - return -len(node.keys) + 1 + result = -len(node.keys) + 1 if sys.version_info >= (3, 8) and isinstance(node, ast.NamedExpr): - return 0 + result = 0 - if isinstance(node, (ast.FormattedValue, ast.JoinedStr)): - return 0 + if isinstance(node, ast.FormattedValue): + result = 0 + if isinstance(node, ast.JoinedStr): + # work around for https://github.com/python/cpython/issues/110309 + result = -( + sum(isinstance(n, ast.Constant) and n.value == "" for n in node.values) + ) if isinstance(node, ast.IfExp): - return -1 + result = -1 if isinstance(node, ast.Subscript): - return 0 + result = 0 if isinstance(node, ast.Index): - return 0 + result = 0 # match if sys.version_info >= (3, 10): if isinstance(node, ast.MatchValue): - return -1 + result = -1 if isinstance(node, (ast.MatchOr, ast.match_case, ast.MatchClass)): - return 0 + result = 0 if isinstance(node, ast.Match): - return -1 # for the subject + result = -1 # for the subject if isinstance(node, ast.MatchMapping): # key-value pairs can only be removed together - return -len(node.patterns) + 1 + result = -len(node.patterns) + 1 # try if sys.version_info >= (3, 11) and isinstance(node, ast.TryStar): # execpt*: is invalid syntax - return -len(node.handlers) + 1 + result = -len(node.handlers) + 1 if isinstance(node, ast.excepthandler): - return 0 + result = 0 + if node.name: + result += 1 if isinstance(node, ast.arguments): # kw_defaults and kwonlyargs can only be removed together - return -len(node.kw_defaults) + result = -len(node.kw_defaults) + + if sys.version_info >= (3, 12): + if isinstance(node, ast.TypeAlias): + result = 0 - return 1 + return result return [(n, weight(n)) for n in ast.walk(tree)] @@ -104,40 +124,30 @@ def count_nodes(source): def try_remove_one(source): node_count = count_nodes(source) - while node_count > 1: - # remove only one "node" from the ast at a time - new_source = minimize( - source, lambda source: count_nodes(source) >= node_count - 1 - ) - - if count_nodes(new_source) != node_count - 1: - return False, source - - source = new_source - - node_count -= 1 - - return True, "" + def checker(source): + try: + compile(source, "", "exec") + except: + return False + count = count_nodes(source) -def test_remove_one_generate(seed): - source = generate(seed, node_limit=1000, depth_limit=6) + if count == node_count - 1: + raise pysource_minimize_testing.StopMinimization - result, source = try_remove_one(source) + return count_nodes(source) >= node_count - 1 - if not result: - # find minimal source where it is not possible to remove one "node" + while node_count > 1: + # remove only one "node" from the ast at a time - def checker(source): - result, failed_source = try_remove_one(source) + with testing_enabled(): + new_source = pysource_minimize_testing.minimize(source, checker, retries=0) - return not result + assert count_nodes(new_source) == node_count - 1 - min_source = minimize(source, checker) + source = new_source - ( - sample_dir / f"{hashlib.sha256(min_source.encode('utf-8')).hexdigest()}.py" - ).write_text(min_source) + node_count -= 1 @pytest.mark.parametrize( @@ -150,13 +160,11 @@ def test_samples(file): try: compile(source, file, "exec") except: - pytest.skip() + pytest.skip("the sample does not compile for the current python version") print("source") print(source) - result, source = try_remove_one(source) - print("\nnew minimized") print(source) print("weights:") @@ -169,4 +177,35 @@ def test_samples(file): else: print(ast.dump(ast.parse(source))) - assert result + try_remove_one(source) + + +def generate_remove_one(): + seed = random.randrange(0, 100000000) + + seed = 1 + + source = generate(seed, node_limit=1000, depth_limit=6) + + try: + try_remove_one(source) + except: + + # find minimal source where it is not possible to remove one "node" + + def checker(source): + assert not pysource_minimize._minimize.TESTING + try: + try_remove_one(source) + except Exception as e: + return True + + return False + + min_source = minimize(source, checker, dbg=True) + + ( + sample_dir / f"{hashlib.sha256(min_source.encode('utf-8')).hexdigest()}.py" + ).write_text(min_source) + + raise ValueError("new sample found") diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..13dc97e --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,13 @@ +from contextlib import contextmanager + +import pysource_minimize._minimize + + +@contextmanager +def testing_enabled(): + old_value = pysource_minimize._minimize.TESTING + pysource_minimize._minimize.TESTING = True + try: + yield + finally: + pysource_minimize._minimize.TESTING = old_value From bdb3226b4739bf6dac83dcfd9a6540335ebfadfb Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:02:47 +0200 Subject: [PATCH 05/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...1fde9e009555f70863931bc0e1a5bac5adb309f131f70a4a27995c060.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/needle_samples/fd27e341fde9e009555f70863931bc0e1a5bac5adb309f131f70a4a27995c060.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 8049b7c..392da24 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -638,7 +638,6 @@ def minimize_stmt(self, node): self.minimize(node.target) return - coverage_required() self.minimize_list(node.body, self.minimize_stmt) body = self.get_ast(node) if not any( diff --git a/tests/needle_samples/fd27e341fde9e009555f70863931bc0e1a5bac5adb309f131f70a4a27995c060.py b/tests/needle_samples/fd27e341fde9e009555f70863931bc0e1a5bac5adb309f131f70a4a27995c060.py new file mode 100644 index 0000000..204744e --- /dev/null +++ b/tests/needle_samples/fd27e341fde9e009555f70863931bc0e1a5bac5adb309f131f70a4a27995c060.py @@ -0,0 +1,2 @@ +for name_4 in needle_17597: + pass \ No newline at end of file From 3a60ccd89e54a7ed5c0dd2be4ee655cf1dfdbcb1 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:07:44 +0200 Subject: [PATCH 06/23] test: coverage --- pysource_minimize/_minimize.py | 2 -- ...b81385bbd1aebd944a5557d245b82d29b454a166bcca1a3a486407e48.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 tests/needle_samples/3517a2bb81385bbd1aebd944a5557d245b82d29b454a166bcca1a3a486407e48.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 392da24..7e0ad90 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -578,7 +578,6 @@ def minimize_stmt(self, node): ): self.minimize(p.bound) return - coverage_required() self.minimize_list(node.type_params, self.minimize_type_param) for e in [ @@ -590,7 +589,6 @@ def minimize_stmt(self, node): ]: if self.try_only(node, e): self.minimize(e) - coverage_required() return coverage_required() diff --git a/tests/needle_samples/3517a2bb81385bbd1aebd944a5557d245b82d29b454a166bcca1a3a486407e48.py b/tests/needle_samples/3517a2bb81385bbd1aebd944a5557d245b82d29b454a166bcca1a3a486407e48.py new file mode 100644 index 0000000..0c1d121 --- /dev/null +++ b/tests/needle_samples/3517a2bb81385bbd1aebd944a5557d245b82d29b454a166bcca1a3a486407e48.py @@ -0,0 +1,2 @@ +class name_0(needle_17597): + pass \ No newline at end of file From cf390931aafa1460dbac8c4f3c87f3649e81c434 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:11:04 +0200 Subject: [PATCH 07/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...6cf9b83219818720791a69cc68465ae9700b9bf3329c6be894daf7705.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/needle_samples/f9676646cf9b83219818720791a69cc68465ae9700b9bf3329c6be894daf7705.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 7e0ad90..4d679d9 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -674,7 +674,6 @@ def minimize_stmt(self, node): for item in node.items: if self.try_only(node, item.context_expr): self.minimize(item.context_expr) - coverage_required() return if item.optional_vars is not None and self.try_only( diff --git a/tests/needle_samples/f9676646cf9b83219818720791a69cc68465ae9700b9bf3329c6be894daf7705.py b/tests/needle_samples/f9676646cf9b83219818720791a69cc68465ae9700b9bf3329c6be894daf7705.py new file mode 100644 index 0000000..1027687 --- /dev/null +++ b/tests/needle_samples/f9676646cf9b83219818720791a69cc68465ae9700b9bf3329c6be894daf7705.py @@ -0,0 +1,2 @@ +with needle_17597: + pass \ No newline at end of file From 94d38796d05919e364fd3a5dcc05096c91075905 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:14:10 +0200 Subject: [PATCH 08/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...50eac38754c0c4ae564556fb57f1e0686d562a590a690751997487aa0.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/needle_samples/16f017150eac38754c0c4ae564556fb57f1e0686d562a590a690751997487aa0.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 4d679d9..0b87a40 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -655,7 +655,6 @@ def minimize_stmt(self, node): isinstance(n, (ast.Break, ast.Continue)) for n in ast.walk(body) ): if self.try_only(node, node.body): - coverage_required() return self.try_only_minimize(node, node.test, node.orelse) diff --git a/tests/needle_samples/16f017150eac38754c0c4ae564556fb57f1e0686d562a590a690751997487aa0.py b/tests/needle_samples/16f017150eac38754c0c4ae564556fb57f1e0686d562a590a690751997487aa0.py new file mode 100644 index 0000000..b1cbf18 --- /dev/null +++ b/tests/needle_samples/16f017150eac38754c0c4ae564556fb57f1e0686d562a590a690751997487aa0.py @@ -0,0 +1,2 @@ +while something: + needle_17597 \ No newline at end of file From c4cafebca5891eaef9e8bbad64cf4938a45dd730 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:16:39 +0200 Subject: [PATCH 09/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...f9180a728bd952cf8c63f713e2b58ac20f84f746bb6b71446b1c7cda.py | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/needle_samples/93e0b352f9180a728bd952cf8c63f713e2b58ac20f84f746bb6b71446b1c7cda.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 0b87a40..07b87e6 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -694,7 +694,6 @@ def minimize_item(item: ast.withitem): for case_ in node.cases: for e in [case_.guard, case_.body]: if e is not None and self.try_only(node, e): - coverage_required() self.minimize(e) return diff --git a/tests/needle_samples/93e0b352f9180a728bd952cf8c63f713e2b58ac20f84f746bb6b71446b1c7cda.py b/tests/needle_samples/93e0b352f9180a728bd952cf8c63f713e2b58ac20f84f746bb6b71446b1c7cda.py new file mode 100644 index 0000000..6e9efd9 --- /dev/null +++ b/tests/needle_samples/93e0b352f9180a728bd952cf8c63f713e2b58ac20f84f746bb6b71446b1c7cda.py @@ -0,0 +1,3 @@ +match name_2: + case name_1.name_5: + needle_17597 \ No newline at end of file From 9610812cc4cda63d1f43467edc83c1058a24195b Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:19:25 +0200 Subject: [PATCH 10/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...d893644693021832272fccc78998f340b0849dd71d013b2054d4df9.py | 4 ++++ tests/test_remove_one.py | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 tests/needle_samples/9c6cbc333d893644693021832272fccc78998f340b0849dd71d013b2054d4df9.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 07b87e6..43343e6 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -744,7 +744,6 @@ def minimize_item(item: ast.withitem): return if h.type is not None and self.try_only(node, h.type): self.minimize(h.type) - coverage_required() return def minimize_except_handler(handler): diff --git a/tests/needle_samples/9c6cbc333d893644693021832272fccc78998f340b0849dd71d013b2054d4df9.py b/tests/needle_samples/9c6cbc333d893644693021832272fccc78998f340b0849dd71d013b2054d4df9.py new file mode 100644 index 0000000..01bd3df --- /dev/null +++ b/tests/needle_samples/9c6cbc333d893644693021832272fccc78998f340b0849dd71d013b2054d4df9.py @@ -0,0 +1,4 @@ +try: + pass +except needle_17597: + pass \ No newline at end of file diff --git a/tests/test_remove_one.py b/tests/test_remove_one.py index 5400786..d63d2dd 100644 --- a/tests/test_remove_one.py +++ b/tests/test_remove_one.py @@ -139,6 +139,7 @@ def checker(source): while node_count > 1: # remove only one "node" from the ast at a time + print("node_count:", node_count) with testing_enabled(): new_source = pysource_minimize_testing.minimize(source, checker, retries=0) @@ -183,8 +184,6 @@ def test_samples(file): def generate_remove_one(): seed = random.randrange(0, 100000000) - seed = 1 - source = generate(seed, node_limit=1000, depth_limit=6) try: From be76e4d2a11251f5e32539d937789e96dd89a848 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:29:28 +0200 Subject: [PATCH 11/23] test: coverage --- pysource_minimize/_minimize.py | 6 ++++-- ...99c35ec1a8bc71cf1cc90d4e2458381ca8d5bbd02da3ea926a15e.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 tests/remove_one_samples/82b5421c26d99c35ec1a8bc71cf1cc90d4e2458381ca8d5bbd02da3ea926a15e.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 43343e6..c473ff2 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -1,6 +1,7 @@ import ast import copy import sys +import warnings from typing import List from typing import Union @@ -591,7 +592,6 @@ def minimize_stmt(self, node): self.minimize(e) return - coverage_required() self.minimize(node.bases) self.minimize_list( node.keywords, terminal=lambda kw: self.minimize(kw.value) @@ -984,7 +984,9 @@ def minimize( def source_checker(new_ast): source = unparse(new_ast) try: - compile(source, "", "exec") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", SyntaxWarning) + compile(source, "", "exec") except: return False diff --git a/tests/remove_one_samples/82b5421c26d99c35ec1a8bc71cf1cc90d4e2458381ca8d5bbd02da3ea926a15e.py b/tests/remove_one_samples/82b5421c26d99c35ec1a8bc71cf1cc90d4e2458381ca8d5bbd02da3ea926a15e.py new file mode 100644 index 0000000..576a4ef --- /dev/null +++ b/tests/remove_one_samples/82b5421c26d99c35ec1a8bc71cf1cc90d4e2458381ca8d5bbd02da3ea926a15e.py @@ -0,0 +1,2 @@ +class name_0(name_1, name_3=name_3): + pass \ No newline at end of file From 4d939adc5b35a5142efb63369ad239a5318df950 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:39:50 +0200 Subject: [PATCH 12/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...be9d653fd527ecde66ae4baca14ad44889df3a026df4a4d3a1cf1294b.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/remove_one_samples/6b35144be9d653fd527ecde66ae4baca14ad44889df3a026df4a4d3a1cf1294b.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index c473ff2..bc6fdc7 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -663,7 +663,6 @@ def minimize_stmt(self, node): pass elif isinstance(node, ast.If): - coverage_required() self.try_only_minimize(node, node.test, node.body, node.orelse) elif isinstance(node, (ast.With, ast.AsyncWith)): diff --git a/tests/remove_one_samples/6b35144be9d653fd527ecde66ae4baca14ad44889df3a026df4a4d3a1cf1294b.py b/tests/remove_one_samples/6b35144be9d653fd527ecde66ae4baca14ad44889df3a026df4a4d3a1cf1294b.py new file mode 100644 index 0000000..c744c68 --- /dev/null +++ b/tests/remove_one_samples/6b35144be9d653fd527ecde66ae4baca14ad44889df3a026df4a4d3a1cf1294b.py @@ -0,0 +1,2 @@ +if name_1: + pass \ No newline at end of file From de2037f80bd84304a28a3479794c38ebeb1cd7c7 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:44:43 +0200 Subject: [PATCH 13/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...98aec849b7c38f15ba3bf7d0449086bd80fe2587b82a617a0100d523d9.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/remove_one_samples/a21a6a98aec849b7c38f15ba3bf7d0449086bd80fe2587b82a617a0100d523d9.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index bc6fdc7..96408cc 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -622,7 +622,6 @@ def minimize_stmt(self, node): self.minimize(child) return - coverage_required() if not self.try_node( node, ast.Assign(targets=[node.target], value=node.value) ): diff --git a/tests/remove_one_samples/a21a6a98aec849b7c38f15ba3bf7d0449086bd80fe2587b82a617a0100d523d9.py b/tests/remove_one_samples/a21a6a98aec849b7c38f15ba3bf7d0449086bd80fe2587b82a617a0100d523d9.py new file mode 100644 index 0000000..a53149d --- /dev/null +++ b/tests/remove_one_samples/a21a6a98aec849b7c38f15ba3bf7d0449086bd80fe2587b82a617a0100d523d9.py @@ -0,0 +1 @@ +(name_2): name_2 = name_0 \ No newline at end of file From 4574e769af2caeb1f50fa6108d49190667286a55 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:47:15 +0200 Subject: [PATCH 14/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...fd930f7df9711589c4ac7dbc410ca7eff56f90f3bb1da83f73ff581.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/needle_samples/19cf4cc7bfd930f7df9711589c4ac7dbc410ca7eff56f90f3bb1da83f73ff581.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 96408cc..eb6aa7b 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -731,7 +731,6 @@ def minimize_item(item: ast.withitem): return if node.finalbody and self.try_only(node, node.finalbody): - coverage_required() self.minimize(node.finalbody) return diff --git a/tests/needle_samples/19cf4cc7bfd930f7df9711589c4ac7dbc410ca7eff56f90f3bb1da83f73ff581.py b/tests/needle_samples/19cf4cc7bfd930f7df9711589c4ac7dbc410ca7eff56f90f3bb1da83f73ff581.py new file mode 100644 index 0000000..b4b3981 --- /dev/null +++ b/tests/needle_samples/19cf4cc7bfd930f7df9711589c4ac7dbc410ca7eff56f90f3bb1da83f73ff581.py @@ -0,0 +1,4 @@ +try: + pass +finally: + needle_17597 \ No newline at end of file From 0adfa03045fdcec22881eb347ded829e38165f00 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:53:59 +0200 Subject: [PATCH 15/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...77ee2c5cedcb14da8a453acd21f6428b22318ebd92818c0be9e6bb1ee.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/needle_samples/a70777077ee2c5cedcb14da8a453acd21f6428b22318ebd92818c0be9e6bb1ee.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index eb6aa7b..98646d2 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -641,7 +641,6 @@ def minimize_stmt(self, node): isinstance(n, (ast.Break, ast.Continue)) for n in ast.walk(body) ): if self.try_only(node, node.body): - coverage_required() return self.try_only_minimize(node, node.iter, node.orelse) diff --git a/tests/needle_samples/a70777077ee2c5cedcb14da8a453acd21f6428b22318ebd92818c0be9e6bb1ee.py b/tests/needle_samples/a70777077ee2c5cedcb14da8a453acd21f6428b22318ebd92818c0be9e6bb1ee.py new file mode 100644 index 0000000..e119f52 --- /dev/null +++ b/tests/needle_samples/a70777077ee2c5cedcb14da8a453acd21f6428b22318ebd92818c0be9e6bb1ee.py @@ -0,0 +1,2 @@ +for name_2 in 0: + needle_17597 \ No newline at end of file From b557979bd1578e1e48a46e35e4df3632a979210c Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 13 Oct 2023 23:56:15 +0200 Subject: [PATCH 16/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...782a916405b723877f04ab0c85ae4624e3639dc599fdd8cefd8ab882f5.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/remove_one_samples/c037b6782a916405b723877f04ab0c85ae4624e3639dc599fdd8cefd8ab882f5.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 98646d2..b6b7e2f 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -625,7 +625,6 @@ def minimize_stmt(self, node): if not self.try_node( node, ast.Assign(targets=[node.target], value=node.value) ): - coverage_required() self.minimize(node.target) self.minimize(node.value) self.minimize(node.annotation) diff --git a/tests/remove_one_samples/c037b6782a916405b723877f04ab0c85ae4624e3639dc599fdd8cefd8ab882f5.py b/tests/remove_one_samples/c037b6782a916405b723877f04ab0c85ae4624e3639dc599fdd8cefd8ab882f5.py new file mode 100644 index 0000000..79d94e1 --- /dev/null +++ b/tests/remove_one_samples/c037b6782a916405b723877f04ab0c85ae4624e3639dc599fdd8cefd8ab882f5.py @@ -0,0 +1 @@ +name_4: name_4 if name_4 else name_0 = name_5 \ No newline at end of file From 0a7b1cdb13c8fd0624fd6c02e4e0851ec2cb6c77 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sat, 14 Oct 2023 00:34:57 +0200 Subject: [PATCH 17/23] test: coverage --- pysource_minimize/_minimize.py | 28 +++++++++++-------- tests/conftest.py | 2 +- ...55cfb278771971f43ae1eb302d22d40106cc130.py | 6 ++++ tests/test_needle.py | 5 ++++ 4 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 tests/needle_samples/8aeb1688ab9e0ca994f6c454f55cfb278771971f43ae1eb302d22d40106cc130.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index b6b7e2f..41ea8d8 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -724,7 +724,6 @@ def minimize_item(item: ast.withitem): return if node.orelse and self.try_only(node, node.orelse): - coverage_required() self.minimize(node.orelse) return @@ -754,20 +753,21 @@ def minimize_except_handler(handler): node.handlers, minimize_except_handler, 1 # 0 if node.finalbody else 1 ) coverage_required() + self.minimize(node.body) self.minimize(node.orelse) self.minimize(node.finalbody) - if try_star and self.try_node( - node, - ast.Try( - body=node.body, - handlers=node.handlers, - orelse=node.orelse, - finalbody=node.finalbody, - ), - ): - return + # if try_star and self.try_node( + # node, + # ast.Try( + # body=node.body, + # handlers=node.handlers, + # orelse=node.orelse, + # finalbody=node.finalbody, + # ), + # ): + # return elif isinstance(node, ast.Assert): if node.msg: @@ -936,7 +936,11 @@ def minimize_ast( current_ast = original_ast while last_success <= retries: minimizer = Minimizer(current_ast, checker, progress_callback) - new_ast = minimizer.get_current_tree({}) + try: + new_ast = minimizer.get_current_tree({}) + except: + print(ast.dump(current_ast, indent=2)) + raise minimized_something = not equal_ast(new_ast, current_ast) diff --git a/tests/conftest.py b/tests/conftest.py index 841eb7f..65e8d36 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,8 +27,8 @@ def pytest_sessionfinish(session, exitstatus): from .test_needle import generate_needle for i in range(2): - generate_remove_one() generate_needle() + generate_remove_one() # teardown_stuff diff --git a/tests/needle_samples/8aeb1688ab9e0ca994f6c454f55cfb278771971f43ae1eb302d22d40106cc130.py b/tests/needle_samples/8aeb1688ab9e0ca994f6c454f55cfb278771971f43ae1eb302d22d40106cc130.py new file mode 100644 index 0000000..225f6f7 --- /dev/null +++ b/tests/needle_samples/8aeb1688ab9e0ca994f6c454f55cfb278771971f43ae1eb302d22d40106cc130.py @@ -0,0 +1,6 @@ +try: + pass +except* name_1: + pass +else: + needle_17597 \ No newline at end of file diff --git a/tests/test_needle.py b/tests/test_needle.py index 2eb8ea6..a901140 100644 --- a/tests/test_needle.py +++ b/tests/test_needle.py @@ -111,6 +111,7 @@ def visit_Match(self, node: ast.Match) -> Any: def generate_needle(): seed = random.randrange(0, 100000000) print("seed:", seed) + seed = 13444151 source = generate(seed, node_limit=10000, depth_limit=6) @@ -148,6 +149,10 @@ def checker(source): except: return False + print() + print("source:") + print(source) + if needle_count(source) != 1: return False try: From 6956c4681f98cf54fa29aa5c9ecdab8b72c6d07b Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sat, 14 Oct 2023 00:38:27 +0200 Subject: [PATCH 18/23] test: coverage --- pysource_minimize/_minimize.py | 7 +------ ...49edef0f9082988825ce805e3d69a91832587c5a39e496123686.py | 4 ++++ tests/test_needle.py | 5 ----- 3 files changed, 5 insertions(+), 11 deletions(-) create mode 100644 tests/needle_samples/aca1af7f625349edef0f9082988825ce805e3d69a91832587c5a39e496123686.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 41ea8d8..40dcfb1 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -734,7 +734,6 @@ def minimize_item(item: ast.withitem): for h in node.handlers: if self.try_only(node, h.body): self.minimize(h.body) - coverage_required() return if h.type is not None and self.try_only(node, h.type): self.minimize(h.type) @@ -936,11 +935,7 @@ def minimize_ast( current_ast = original_ast while last_success <= retries: minimizer = Minimizer(current_ast, checker, progress_callback) - try: - new_ast = minimizer.get_current_tree({}) - except: - print(ast.dump(current_ast, indent=2)) - raise + new_ast = minimizer.get_current_tree({}) minimized_something = not equal_ast(new_ast, current_ast) diff --git a/tests/needle_samples/aca1af7f625349edef0f9082988825ce805e3d69a91832587c5a39e496123686.py b/tests/needle_samples/aca1af7f625349edef0f9082988825ce805e3d69a91832587c5a39e496123686.py new file mode 100644 index 0000000..d3fc0b3 --- /dev/null +++ b/tests/needle_samples/aca1af7f625349edef0f9082988825ce805e3d69a91832587c5a39e496123686.py @@ -0,0 +1,4 @@ +try: + pass +except* (name_3 for name_5 in name_0 if name_0): + needle_17597 \ No newline at end of file diff --git a/tests/test_needle.py b/tests/test_needle.py index a901140..2eb8ea6 100644 --- a/tests/test_needle.py +++ b/tests/test_needle.py @@ -111,7 +111,6 @@ def visit_Match(self, node: ast.Match) -> Any: def generate_needle(): seed = random.randrange(0, 100000000) print("seed:", seed) - seed = 13444151 source = generate(seed, node_limit=10000, depth_limit=6) @@ -149,10 +148,6 @@ def checker(source): except: return False - print() - print("source:") - print(source) - if needle_count(source) != 1: return False try: From a16ca07034e04534e2c9517f7d7b07d0a43f6d56 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sat, 14 Oct 2023 00:57:32 +0200 Subject: [PATCH 19/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...35a99e6650ccb6b2d96b19afcadac04ee33d2f18f3ae900cdd8.py | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 tests/remove_one_samples/aab70cf41133735a99e6650ccb6b2d96b19afcadac04ee33d2f18f3ae900cdd8.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index 40dcfb1..b073e06 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -751,7 +751,6 @@ def minimize_except_handler(handler): self.minimize_list( node.handlers, minimize_except_handler, 1 # 0 if node.finalbody else 1 ) - coverage_required() self.minimize(node.body) self.minimize(node.orelse) diff --git a/tests/remove_one_samples/aab70cf41133735a99e6650ccb6b2d96b19afcadac04ee33d2f18f3ae900cdd8.py b/tests/remove_one_samples/aab70cf41133735a99e6650ccb6b2d96b19afcadac04ee33d2f18f3ae900cdd8.py new file mode 100644 index 0000000..e5a7daf --- /dev/null +++ b/tests/remove_one_samples/aab70cf41133735a99e6650ccb6b2d96b19afcadac04ee33d2f18f3ae900cdd8.py @@ -0,0 +1,8 @@ +try: + pass +except: + pass +else: + name_5 +finally: + name_1 \ No newline at end of file From bbaa3308420d47b1e58e2985c993ea47c23714d4 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sat, 14 Oct 2023 08:20:58 +0200 Subject: [PATCH 20/23] test: coverage --- pysource_minimize/_minimize.py | 1 - ...113e276d3babd51166ed33d9ccfb506f4aa366302451defbfaf7f3f72.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/needle_samples/8937599113e276d3babd51166ed33d9ccfb506f4aa366302451defbfaf7f3f72.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index b073e06..b2cf51f 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -599,7 +599,6 @@ def minimize_stmt(self, node): return elif isinstance(node, ast.Return): - coverage_required() self.try_only_minimize(node, node.value) elif isinstance(node, ast.Delete): diff --git a/tests/needle_samples/8937599113e276d3babd51166ed33d9ccfb506f4aa366302451defbfaf7f3f72.py b/tests/needle_samples/8937599113e276d3babd51166ed33d9ccfb506f4aa366302451defbfaf7f3f72.py new file mode 100644 index 0000000..757d978 --- /dev/null +++ b/tests/needle_samples/8937599113e276d3babd51166ed33d9ccfb506f4aa366302451defbfaf7f3f72.py @@ -0,0 +1,2 @@ +def name_3(): + return needle_17597 \ No newline at end of file From f08b0eece09d04e5d3fa1906552ca81be837a510 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Sat, 14 Oct 2023 14:40:03 +0200 Subject: [PATCH 21/23] fix: except-handler-type --- pysource_minimize/_minimize.py | 12 +++++++----- ...e8b5954c68765b6d1637fe34ac20323f0a90a58b49c5a0.py | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 tests/remove_one_samples/ac97d98b0bb3b09113e8b5954c68765b6d1637fe34ac20323f0a90a58b49c5a0.py diff --git a/pysource_minimize/_minimize.py b/pysource_minimize/_minimize.py index b2cf51f..5bba67b 100644 --- a/pysource_minimize/_minimize.py +++ b/pysource_minimize/_minimize.py @@ -38,6 +38,11 @@ class CoverageRequired(Exception): pass +def coverage_required(): + if TESTING: + raise CoverageRequired() + + def equal_ast(lhs, rhs): if type(lhs) != type(rhs): return False @@ -59,11 +64,6 @@ def equal_ast(lhs, rhs): assert False, f"unexpected type {type(lhs)}" -def coverage_required(): - if TESTING: - raise CoverageRequired() - - def arguments( node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda] ) -> List[ast.arg]: @@ -743,6 +743,8 @@ def minimize_except_handler(handler): if not handler.name and not try_star: self.minimize_optional(handler.type) + elif handler.type is not None: + self.minimize(handler.type) if handler.name: self.try_attr(handler, "name", None) diff --git a/tests/remove_one_samples/ac97d98b0bb3b09113e8b5954c68765b6d1637fe34ac20323f0a90a58b49c5a0.py b/tests/remove_one_samples/ac97d98b0bb3b09113e8b5954c68765b6d1637fe34ac20323f0a90a58b49c5a0.py new file mode 100644 index 0000000..dd3a5bd --- /dev/null +++ b/tests/remove_one_samples/ac97d98b0bb3b09113e8b5954c68765b6d1637fe34ac20323f0a90a58b49c5a0.py @@ -0,0 +1,6 @@ +try: + pass +except* lambda name_4, /, name_1=name_0, *name_5: name_4: + pass +except* (name_2 for name_4 in name_4 if name_3): + pass \ No newline at end of file From 3c5cccc24398267d3a248ac3e4250662626f2b77 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 20 Oct 2023 17:12:44 +0200 Subject: [PATCH 22/23] feat: use commitizen for versioning --- pyproject.toml | 10 ++++++++++ pysource_minimize/__init__.py | 3 +++ 2 files changed, 13 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index aacc1c6..b2b6811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,16 @@ license = "MIT" readme = "README.md" packages = [{include = "pysource_minimize"}] +[tool.commitizen] +changelog_incremental = true +major_version_zero = true +tag_format = "v$major.$minor.$patch$prerelease" +update_changelog_on_bump = true +version_files = [ + "inline_snapshot/__init__.py:version" +] +version_provider = "poetry" + [tool.poetry.dependencies] python = "^3.7" asttokens = "^2.0.8" diff --git a/pysource_minimize/__init__.py b/pysource_minimize/__init__.py index bbf83eb..8c7fda2 100644 --- a/pysource_minimize/__init__.py +++ b/pysource_minimize/__init__.py @@ -3,3 +3,6 @@ __all__ = ("minimize", "StopMinimization") + + +version = "0.4.0" From 052c8b17dec56b8cfcbdf56ffe3fab54ffb875e9 Mon Sep 17 00:00:00 2001 From: Frank Hoffmann <15r10nk-git@polarbit.de> Date: Fri, 20 Oct 2023 17:18:18 +0200 Subject: [PATCH 23/23] chore: bump to 0.5.0 --- CHANGELOG.md | 17 +++++++++++++++++ pyproject.toml | 4 ++-- pysource_minimize/__init__.py | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1334fc6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +## v0.5.0 (2023-10-20) + +### Feat + +- support for 3.12 + +### Fix + +- fixed various bugs + +## v0.4.0 (2023-09-21) + +### Feat + +- implemented cli +- support for python 3.7 - 3.11 +- progress callback for minimize diff --git a/pyproject.toml b/pyproject.toml index b2b6811..4259149 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pysource-minimize" -version = "0.4.0" +version = "0.5.0" description = "minimize python source code" authors = ["Frank Hoffmann"] license = "MIT" @@ -13,7 +13,7 @@ major_version_zero = true tag_format = "v$major.$minor.$patch$prerelease" update_changelog_on_bump = true version_files = [ - "inline_snapshot/__init__.py:version" + "pysource_minimize/__init__.py:version" ] version_provider = "poetry" diff --git a/pysource_minimize/__init__.py b/pysource_minimize/__init__.py index 8c7fda2..070f896 100644 --- a/pysource_minimize/__init__.py +++ b/pysource_minimize/__init__.py @@ -5,4 +5,4 @@ __all__ = ("minimize", "StopMinimization") -version = "0.4.0" +version = "0.5.0"