From 3897c05d47bb1c448b2a8e8030e98403176128b4 Mon Sep 17 00:00:00 2001 From: aayan636 Date: Sun, 5 Jul 2026 23:02:12 -0700 Subject: [PATCH 1/7] Add a test suite, add one example test for transformer --- .vscode/launch.json | 15 ++++ .vscode/settings.json | 4 + src/analysis/transform/transformer.py | 6 +- src/analysis/transform/transformer_assign.py | 21 +++-- src/analysis/transform/transformer_expr.py | 22 ++--- src/analysis/transform/transformer_helpers.py | 20 +++-- test/analysis/transform/inputs/if_simple.py | 4 + test/analysis/transform/outputs/if_simple.py | 7 ++ test/analysis/transform/test_transform.py | 83 +++++++++++++++++++ 9 files changed, 152 insertions(+), 30 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 test/analysis/transform/inputs/if_simple.py create mode 100644 test/analysis/transform/outputs/if_simple.py create mode 100644 test/analysis/transform/test_transform.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a78fb2e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Transformer", + "type": "debugpy", + "request": "launch", + "module": "src.analysis.transform.transformer", + "console": "integratedTerminal", + "env": { + "PYTHONPATH": "${workspaceFolder}/src" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4b5a294 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:conda", + "python-envs.defaultPackageManager": "ms-python.python:conda" +} \ No newline at end of file diff --git a/src/analysis/transform/transformer.py b/src/analysis/transform/transformer.py index fad2eb3..6f75d8b 100644 --- a/src/analysis/transform/transformer.py +++ b/src/analysis/transform/transformer.py @@ -262,8 +262,10 @@ def transform_code(source_code: str) -> str: # print(i) test_code = """ -def foo(x, *k, **y): - pass +if x: + y = 1 +else: + y = 2 """ # a = [x * 2 for x in range(5)] diff --git a/src/analysis/transform/transformer_assign.py b/src/analysis/transform/transformer_assign.py index 25e8d52..554cbab 100644 --- a/src/analysis/transform/transformer_assign.py +++ b/src/analysis/transform/transformer_assign.py @@ -5,25 +5,26 @@ class SidewinderAssignTransformerMixin(SidewinderTransformerHelpers): - def visit_Assign(self, node: ast.Assign) -> None: + def visit_Assign(self, node: ast.Assign) -> list[ast.stmt]: """Transform assignment statement - transform value.""" transformed_value = self._visit_expr(node.value) if len(node.targets) != 1: raise NotImplementedError(f"Sidewinder currently only supports a single target for assign statements like {ast.unparse(node)}") + to_return = [] for target in node.targets: - self._visit_target(target, transformed_value) - return None + to_return.extend(self._visit_target(target, transformed_value)) + return to_return - def visit_AnnAssign(self, node: ast.AnnAssign) -> None: + def visit_AnnAssign(self, node: ast.AnnAssign) -> list[ast.stmt]: """Transform annotated assignment statement.""" # Transform the annotation (it's an expr) node.annotation = self._visit_expr(node.annotation) # Transform the value if it exists (can be None) if node.value is not None: - self._visit_target(node.target, self._visit_expr(node.value)) - return None + return self._visit_target(node.target, self._visit_expr(node.value)) + return [] - def visit_AugAssign(self, node: ast.AugAssign) -> None: + def visit_AugAssign(self, node: ast.AugAssign) -> list[ast.stmt]: op_map = { ast.Add: 'add', ast.Sub: 'sub', @@ -64,11 +65,13 @@ def visit_AugAssign(self, node: ast.AugAssign) -> None: ) tmp = self._fresh_temp() - self.current_context.append_stmt( + to_return = [] + to_return.append( ast.Assign( targets=[ast.Name(id=tmp, ctx=ast.Store())], value=rhs_value, lineno=0, col_offset=0, ) ) - self._visit_target(node.target, ast.Name(id=tmp, ctx=ast.Load())) + to_return.extend(self._visit_target(node.target, ast.Name(id=tmp, ctx=ast.Load()))) + return to_return diff --git a/src/analysis/transform/transformer_expr.py b/src/analysis/transform/transformer_expr.py index 72d8f8e..c43a10e 100644 --- a/src/analysis/transform/transformer_expr.py +++ b/src/analysis/transform/transformer_expr.py @@ -246,7 +246,7 @@ def visit_Subscript(self, node: ast.Subscript) -> tuple[list[ast.stmt], ast.expr def visit_Slice(self, node: ast.Slice) -> Any: """Transform slice - convert to slice object.""" # a[1:5:2] -> slice(1, 5, 2) - return ast.Call( + return [], ast.Call( func=ast.Name(id='slice', ctx=ast.Load()), args=[ self._visit_expr(node.lower) if node.lower else ast.Constant(value=None), @@ -359,7 +359,7 @@ def visit_Lambda(self, node: ast.Lambda) -> Any: # Transform body node.body = self._visit_expr(node.body) - return node + return [], node def visit_IfExp(self, node: ast.IfExp) -> tuple[list[ast.stmt], ast.expr]: result = self._fresh_temp("__sidewinder_ifexp") @@ -403,22 +403,22 @@ def visit_Dict(self, node: ast.Dict) -> Any: assert (k is not None for k in node.keys), "Unpacking dict is not supported yet" node.keys = [self._visit_expr(k) if k else None for k in node.keys] node.values = [self._visit_expr(v) for v in node.values] - return node + return [], node def visit_Set(self, node: ast.Set) -> Any: """Transform set literal.""" node.elts = [self._visit_expr(elt) for elt in node.elts] - return node + return [], node def visit_List(self, node: ast.List) -> Any: """Transform list literal.""" node.elts = [self._visit_expr(elt) for elt in node.elts] - return node + return [], node def visit_Tuple(self, node: ast.Tuple) -> Any: """Transform tuple literal.""" node.elts = [self._visit_expr(elt) for elt in node.elts] - return node + return [], node def visit_Await(self, node: ast.Await) -> Any: """Transform await expression.""" @@ -429,25 +429,25 @@ def visit_FormattedValue(self, node: ast.FormattedValue) -> Any: node.value = self._visit_expr(node.value) if node.format_spec: node.format_spec = self._visit_expr(node.format_spec) - return node + return [], node def visit_JoinedStr(self, node: ast.JoinedStr) -> Any: """Transform f-string.""" node.values = [self._visit_expr(val) for val in node.values] - return node + return [], node def visit_Starred(self, node: ast.Starred) -> Any: """Transform starred expression.""" node.value = self._visit_expr(node.value) - return node + return [], node def visit_Name(self, node: ast.Name) -> Any: """Name nodes are unchanged.""" - return node + return [], node def visit_Constant(self, node: ast.Constant) -> Any: """Constant nodes are unchanged.""" - return node + return [], node def visit_NamedExpr(self, node: ast.NamedExpr) -> Any: """Transform named expression (walrus operator).""" diff --git a/src/analysis/transform/transformer_helpers.py b/src/analysis/transform/transformer_helpers.py index 5f916a7..ad1d049 100644 --- a/src/analysis/transform/transformer_helpers.py +++ b/src/analysis/transform/transformer_helpers.py @@ -90,7 +90,7 @@ def _visit_expr(self, expr: T) -> ast.expr: self.current_context.append_stmt(stmt) return final_expr - def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: ast.expr) -> None: + def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: ast.expr) -> List[ast.stmt]: """ Emit statements to assign rhs to target. rhs is already a normalized expression (usually a Name node pointing to a tmp). @@ -104,32 +104,34 @@ def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: as match target: case ast.Name(): # x = rhs — base case, plain assignment, no transformation needed - self.current_context.append_stmt(ast.Assign(targets=[target], value=visited_rhs, lineno=0, col_offset=0)) + return [ast.Assign(targets=[target], value=visited_rhs, lineno=0, col_offset=0)] case ast.Attribute(): # y.attr = rhs → _visit_expr(y).__setattr__("attr", rhs) obj = self._visit_expr(target.value) - self.current_context.append_stmt( + return [ ast.Expr(value=ast.Call( func=ast.Attribute(value=obj, attr="__sidewinder_setattr__", ctx=ast.Load()), args=[ast.Constant(value = target.attr), visited_rhs], keywords=[], ), lineno=0, col_offset=0) - ) + ] case ast.Subscript(): # y[i] = rhs → _visit_expr(y).__setitem__(_visit_expr(i), rhs) obj = self._visit_expr(target.value) idx = self._visit_expr(target.slice) - self.current_context.append_stmt( + return [ ast.Expr(value=ast.Call( func=ast.Attribute(value=obj, attr="__sidewinder_setitem__", ctx=ast.Load()), args=[idx, visited_rhs], keywords=[], ), lineno=0, col_offset=0) - ) + ] case ast.Tuple() | ast.List(): + to_return = [] + starred_indices = [i for i, e in enumerate(target.elts) if isinstance(e, ast.Starred)] @@ -137,7 +139,7 @@ def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: as # simple case: x, y, z = rhs # emit: _iter = visited_rhs.__iter__() iter_tmp = self._fresh_temp() - self.current_context.append_stmt( + to_return.append( ast.Assign( targets=[ast.Name(id=iter_tmp, ctx=ast.Store())], value=ast.Call( @@ -150,7 +152,7 @@ def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: as for elt in target.elts: # emit: _tmpN = _iter.__next__() next_tmp = self._fresh_temp() - self.current_context.append_stmt( + to_return.append( ast.Assign( targets=[ast.Name(id=next_tmp, ctx=ast.Store())], value=ast.Call( @@ -164,7 +166,9 @@ def _visit_target(self, target: ast.expr | ast.Tuple | ast.List, visited_rhs: as lineno=0, col_offset=0, ) ) + assert False, "This does not look right, fix when you get to it" self._visit_target(elt, ast.Name(id=next_tmp, ctx=ast.Load())) + return to_return elif len(starred_indices) == 1: raise NotImplementedError("starred unpacking in tuple target") diff --git a/test/analysis/transform/inputs/if_simple.py b/test/analysis/transform/inputs/if_simple.py new file mode 100644 index 0000000..ab8f4eb --- /dev/null +++ b/test/analysis/transform/inputs/if_simple.py @@ -0,0 +1,4 @@ +if x: + y = 1 +else: + y = 2 \ No newline at end of file diff --git a/test/analysis/transform/outputs/if_simple.py b/test/analysis/transform/outputs/if_simple.py new file mode 100644 index 0000000..e016d02 --- /dev/null +++ b/test/analysis/transform/outputs/if_simple.py @@ -0,0 +1,7 @@ +__sidewinder_cond0 = x +__sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y = 1 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y = 2 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/test_transform.py b/test/analysis/transform/test_transform.py new file mode 100644 index 0000000..d681345 --- /dev/null +++ b/test/analysis/transform/test_transform.py @@ -0,0 +1,83 @@ +# testDriver.py +import ast +import difflib +import pytest +import warnings +from pathlib import Path +from analysis.transform.transformer import SidewinderTransformer # adjust import path + +INPUTS_DIR = Path(__file__).parent / "inputs" +OUTPUTS_DIR = Path(__file__).parent / "outputs" + + +def discover_tests() -> list[tuple[Path, Path]]: + """ + Discover input/output test pairs. + Flags: + - inputs with no matching expected output + - expected outputs with no matching input + """ + input_files = set(INPUTS_DIR.glob("*.py")) + output_files = set(OUTPUTS_DIR.glob("*-expected.py")) + + # map stem -> path for inputs + input_map = {f.stem: f for f in input_files} + # map stem (without -expected) -> path for outputs + output_map = {f.stem.removesuffix("-expected"): f for f in output_files} + + # flag unmatched inputs + for stem, path in input_map.items(): + if stem not in output_map: + warnings.warn(f"Input file {path.name} has no matching expected output — skipping") + + # flag unmatched outputs + for stem, path in output_map.items(): + if stem not in input_map: + warnings.warn(f"Expected output {path.name} has no matching input — skipping") + + # return matched pairs only + return [ + (input_map[stem], output_map[stem]) + for stem in input_map + if stem in output_map + ] + + +def _parse_file(path: Path) -> ast.Module: + return ast.parse(path.read_text()) + + +def _diff(expected: ast.AST, actual: ast.AST) -> str: + expected_src = ast.unparse(expected) + actual_src = ast.unparse(actual) + diff = difflib.unified_diff( + expected_src.splitlines(keepends=True), + actual_src.splitlines(keepends=True), + fromfile="expected", + tofile="actual", + ) + return "".join(diff) + + +@pytest.mark.parametrize("input_file,expected_file", discover_tests()) +def test_transformer(input_file: Path, expected_file: Path) -> None: + # parse input + input_tree = _parse_file(input_file) + + # transform + transformer = SidewinderTransformer() + actual_tree = transformer.visit(input_tree) + ast.fix_missing_locations(actual_tree) + + # parse expected output + expected_tree = _parse_file(expected_file) + + # compare structurally + if ast.dump(expected_tree) != ast.dump(actual_tree): + diff = _diff(expected_tree, actual_tree) + pytest.fail( + f"\nTransformer output does not match expected for {input_file.name}\n" + f"\n--- DIFF ---\n{diff}\n" + f"\n--- EXPECTED ---\n{ast.unparse(expected_tree)}\n" + f"\n--- ACTUAL ---\n{ast.unparse(actual_tree)}\n" + ) \ No newline at end of file From b9dec3fd18fc70a1c325712f83f15bc00d09bd0e Mon Sep 17 00:00:00 2001 From: aayan636 Date: Sun, 5 Jul 2026 23:22:05 -0700 Subject: [PATCH 2/7] First test pass --- .vscode/launch.json | 14 ++++++++++++++ .../{if_simple.py => if_simple_expected.py} | 2 ++ test/analysis/transform/test_transform.py | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) rename test/analysis/transform/outputs/{if_simple.py => if_simple_expected.py} (84%) diff --git a/.vscode/launch.json b/.vscode/launch.json index a78fb2e..6d6caa6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,6 +10,20 @@ "env": { "PYTHONPATH": "${workspaceFolder}/src" } + }, + { + "name": "Python Debugger: Tests", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "test/", + "-v" + ], + "console": "integratedTerminal", + "env": { + "PYTHONPATH": "${workspaceFolder}/src" + } } ] } \ No newline at end of file diff --git a/test/analysis/transform/outputs/if_simple.py b/test/analysis/transform/outputs/if_simple_expected.py similarity index 84% rename from test/analysis/transform/outputs/if_simple.py rename to test/analysis/transform/outputs/if_simple_expected.py index e016d02..da08f76 100644 --- a/test/analysis/transform/outputs/if_simple.py +++ b/test/analysis/transform/outputs/if_simple_expected.py @@ -1,3 +1,5 @@ +from sidewinder.analysis.symbolic.state import SidewinderState + __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) y = 1 diff --git a/test/analysis/transform/test_transform.py b/test/analysis/transform/test_transform.py index d681345..33f8190 100644 --- a/test/analysis/transform/test_transform.py +++ b/test/analysis/transform/test_transform.py @@ -18,12 +18,12 @@ def discover_tests() -> list[tuple[Path, Path]]: - expected outputs with no matching input """ input_files = set(INPUTS_DIR.glob("*.py")) - output_files = set(OUTPUTS_DIR.glob("*-expected.py")) + output_files = set(OUTPUTS_DIR.glob("*_expected.py")) # map stem -> path for inputs input_map = {f.stem: f for f in input_files} # map stem (without -expected) -> path for outputs - output_map = {f.stem.removesuffix("-expected"): f for f in output_files} + output_map = {f.stem.removesuffix("_expected"): f for f in output_files} # flag unmatched inputs for stem, path in input_map.items(): From e6532c162629562f82dd02abe08bf00089ed4153 Mon Sep 17 00:00:00 2001 From: aayan636 Date: Mon, 6 Jul 2026 07:06:22 -0700 Subject: [PATCH 3/7] add some unit tests for IF --- test/analysis/transform/inputs/if/if_elif.py | 6 +++++ .../transform/inputs/if/if_elif_no_else.py | 4 ++++ .../transform/inputs/if/if_multiple_elif.py | 8 +++++++ .../transform/inputs/if/if_nested_else.py | 7 ++++++ .../transform/inputs/if/if_nested_if.py | 7 ++++++ .../analysis/transform/inputs/if/if_simple.py | 4 ++++ .../transform/inputs/if/if_without_else.py | 2 ++ test/analysis/transform/inputs/if_simple.py | 4 ---- .../transform/outputs/if/if_elif_expected.py | 15 +++++++++++++ .../outputs/if/if_elif_no_else_expected.py | 12 ++++++++++ .../outputs/if/if_multiple_elif_expected.py | 22 +++++++++++++++++++ .../outputs/if/if_nested_else_expected.py | 17 ++++++++++++++ .../outputs/if/if_nested_if_expected.py | 16 ++++++++++++++ .../outputs/{ => if}/if_simple_expected.py | 4 ++-- .../outputs/if/if_without_else_expected.py | 6 +++++ test/analysis/transform/test_transform.py | 11 +++++++--- 16 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 test/analysis/transform/inputs/if/if_elif.py create mode 100644 test/analysis/transform/inputs/if/if_elif_no_else.py create mode 100644 test/analysis/transform/inputs/if/if_multiple_elif.py create mode 100644 test/analysis/transform/inputs/if/if_nested_else.py create mode 100644 test/analysis/transform/inputs/if/if_nested_if.py create mode 100644 test/analysis/transform/inputs/if/if_simple.py create mode 100644 test/analysis/transform/inputs/if/if_without_else.py delete mode 100644 test/analysis/transform/inputs/if_simple.py create mode 100644 test/analysis/transform/outputs/if/if_elif_expected.py create mode 100644 test/analysis/transform/outputs/if/if_elif_no_else_expected.py create mode 100644 test/analysis/transform/outputs/if/if_multiple_elif_expected.py create mode 100644 test/analysis/transform/outputs/if/if_nested_else_expected.py create mode 100644 test/analysis/transform/outputs/if/if_nested_if_expected.py rename test/analysis/transform/outputs/{ => if}/if_simple_expected.py (97%) create mode 100644 test/analysis/transform/outputs/if/if_without_else_expected.py diff --git a/test/analysis/transform/inputs/if/if_elif.py b/test/analysis/transform/inputs/if/if_elif.py new file mode 100644 index 0000000..73885e6 --- /dev/null +++ b/test/analysis/transform/inputs/if/if_elif.py @@ -0,0 +1,6 @@ +if x: + y1 +elif z: + y2 +else: + y3 \ No newline at end of file diff --git a/test/analysis/transform/inputs/if/if_elif_no_else.py b/test/analysis/transform/inputs/if/if_elif_no_else.py new file mode 100644 index 0000000..4bfe337 --- /dev/null +++ b/test/analysis/transform/inputs/if/if_elif_no_else.py @@ -0,0 +1,4 @@ +if x: + y1 +elif z: + y2 \ No newline at end of file diff --git a/test/analysis/transform/inputs/if/if_multiple_elif.py b/test/analysis/transform/inputs/if/if_multiple_elif.py new file mode 100644 index 0000000..557cfaf --- /dev/null +++ b/test/analysis/transform/inputs/if/if_multiple_elif.py @@ -0,0 +1,8 @@ +if x: + y1 +elif z: + y2 +elif p: + y3 +else: + y4 \ No newline at end of file diff --git a/test/analysis/transform/inputs/if/if_nested_else.py b/test/analysis/transform/inputs/if/if_nested_else.py new file mode 100644 index 0000000..da0f722 --- /dev/null +++ b/test/analysis/transform/inputs/if/if_nested_else.py @@ -0,0 +1,7 @@ +if x: + y1 +else: + if z: + y2 + else: + y3 \ No newline at end of file diff --git a/test/analysis/transform/inputs/if/if_nested_if.py b/test/analysis/transform/inputs/if/if_nested_if.py new file mode 100644 index 0000000..4ee6a8b --- /dev/null +++ b/test/analysis/transform/inputs/if/if_nested_if.py @@ -0,0 +1,7 @@ +if x: + if z: + y1 + else: + y3 +else: + y2 \ No newline at end of file diff --git a/test/analysis/transform/inputs/if/if_simple.py b/test/analysis/transform/inputs/if/if_simple.py new file mode 100644 index 0000000..0be57fd --- /dev/null +++ b/test/analysis/transform/inputs/if/if_simple.py @@ -0,0 +1,4 @@ +if x: + y1 +else: + y2 \ No newline at end of file diff --git a/test/analysis/transform/inputs/if/if_without_else.py b/test/analysis/transform/inputs/if/if_without_else.py new file mode 100644 index 0000000..249d9b3 --- /dev/null +++ b/test/analysis/transform/inputs/if/if_without_else.py @@ -0,0 +1,2 @@ +if x: + y1 \ No newline at end of file diff --git a/test/analysis/transform/inputs/if_simple.py b/test/analysis/transform/inputs/if_simple.py deleted file mode 100644 index ab8f4eb..0000000 --- a/test/analysis/transform/inputs/if_simple.py +++ /dev/null @@ -1,4 +0,0 @@ -if x: - y = 1 -else: - y = 2 \ No newline at end of file diff --git a/test/analysis/transform/outputs/if/if_elif_expected.py b/test/analysis/transform/outputs/if/if_elif_expected.py new file mode 100644 index 0000000..c60151d --- /dev/null +++ b/test/analysis/transform/outputs/if/if_elif_expected.py @@ -0,0 +1,15 @@ +from sidewinder.analysis.symbolic.state import SidewinderState + +__sidewinder_cond0 = x +__sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y1 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +__sidewinder_cond1 = z +__sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y2 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y3 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/if/if_elif_no_else_expected.py b/test/analysis/transform/outputs/if/if_elif_no_else_expected.py new file mode 100644 index 0000000..e44afe0 --- /dev/null +++ b/test/analysis/transform/outputs/if/if_elif_no_else_expected.py @@ -0,0 +1,12 @@ +from sidewinder.analysis.symbolic.state import SidewinderState + +__sidewinder_cond0 = x +__sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y1 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +__sidewinder_cond1 = z +__sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y2 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/if/if_multiple_elif_expected.py b/test/analysis/transform/outputs/if/if_multiple_elif_expected.py new file mode 100644 index 0000000..380df62 --- /dev/null +++ b/test/analysis/transform/outputs/if/if_multiple_elif_expected.py @@ -0,0 +1,22 @@ +from sidewinder.analysis.symbolic.state import SidewinderState + +__sidewinder_cond0 = x +__sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y1 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +__sidewinder_cond1 = z +__sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y2 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) + +__sidewinder_cond2 = p +__sidewinder_condition_true__(__sidewinder_cond2, __sidewinder_state=__sidewinder_state) +y3 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond2, __sidewinder_state=__sidewinder_state) +y4 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/if/if_nested_else_expected.py b/test/analysis/transform/outputs/if/if_nested_else_expected.py new file mode 100644 index 0000000..2c2a7cd --- /dev/null +++ b/test/analysis/transform/outputs/if/if_nested_else_expected.py @@ -0,0 +1,17 @@ +from sidewinder.analysis.symbolic.state import SidewinderState + +__sidewinder_cond0 = x +__sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y1 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) + +__sidewinder_cond1 = z +__sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y2 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y3 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) + +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/if/if_nested_if_expected.py b/test/analysis/transform/outputs/if/if_nested_if_expected.py new file mode 100644 index 0000000..11b415a --- /dev/null +++ b/test/analysis/transform/outputs/if/if_nested_if_expected.py @@ -0,0 +1,16 @@ +from sidewinder.analysis.symbolic.state import SidewinderState + +__sidewinder_cond0 = x +__sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) + +__sidewinder_cond1 = z +__sidewinder_condition_true__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y1 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond1, __sidewinder_state=__sidewinder_state) +y3 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) +__sidewinder_condition_false__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y2 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/if_simple_expected.py b/test/analysis/transform/outputs/if/if_simple_expected.py similarity index 97% rename from test/analysis/transform/outputs/if_simple_expected.py rename to test/analysis/transform/outputs/if/if_simple_expected.py index da08f76..b990c84 100644 --- a/test/analysis/transform/outputs/if_simple_expected.py +++ b/test/analysis/transform/outputs/if/if_simple_expected.py @@ -2,8 +2,8 @@ __sidewinder_cond0 = x __sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) -y = 1 +y1 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) __sidewinder_condition_false__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) -y = 2 +y2 __sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/outputs/if/if_without_else_expected.py b/test/analysis/transform/outputs/if/if_without_else_expected.py new file mode 100644 index 0000000..19322cc --- /dev/null +++ b/test/analysis/transform/outputs/if/if_without_else_expected.py @@ -0,0 +1,6 @@ +from sidewinder.analysis.symbolic.state import SidewinderState + +__sidewinder_cond0 = x +__sidewinder_condition_true__(__sidewinder_cond0, __sidewinder_state=__sidewinder_state) +y1 +__sidewinder_pop_condition__(__sidewinder_state=__sidewinder_state) \ No newline at end of file diff --git a/test/analysis/transform/test_transform.py b/test/analysis/transform/test_transform.py index 33f8190..ececb31 100644 --- a/test/analysis/transform/test_transform.py +++ b/test/analysis/transform/test_transform.py @@ -17,8 +17,8 @@ def discover_tests() -> list[tuple[Path, Path]]: - inputs with no matching expected output - expected outputs with no matching input """ - input_files = set(INPUTS_DIR.glob("*.py")) - output_files = set(OUTPUTS_DIR.glob("*_expected.py")) + input_files = set(INPUTS_DIR.glob("**/*.py")) + output_files = set(OUTPUTS_DIR.glob("**/*_expected.py")) # map stem -> path for inputs input_map = {f.stem: f for f in input_files} @@ -58,8 +58,13 @@ def _diff(expected: ast.AST, actual: ast.AST) -> str: ) return "".join(diff) +test_cases = discover_tests() -@pytest.mark.parametrize("input_file,expected_file", discover_tests()) +@pytest.mark.parametrize( + "input_file,expected_file", + test_cases, + ids=[f"{input_file.parent.stem}::{input_file.stem}" for input_file, _ in test_cases] +) def test_transformer(input_file: Path, expected_file: Path) -> None: # parse input input_tree = _parse_file(input_file) From 9809de2ce4b57cb33f59fa5ef0b61d5173fbf040 Mon Sep 17 00:00:00 2001 From: aayan636 Date: Mon, 6 Jul 2026 07:32:55 -0700 Subject: [PATCH 4/7] better project structure (move everything to sidewinder folder for future fithub actions) --- .python-version | 2 +- .vscode/launch.json | 13 +++----- pyproject.toml | 19 +++++++---- src/analysis/symbolic/lattice/final_string.py | 4 --- .../symbolic/runtime/values/heap_object.py | 11 ------- .../symbolic/runtime/values/symbolic_value.py | 14 -------- src/{ => sidewinder}/analysis/__init__.py | 4 +-- src/{ => sidewinder}/analysis/analyzer.py | 2 +- .../analysis/symbolic/__init__.py | 0 .../analysis/symbolic/assumption.py | 0 .../analysis/symbolic/callable_value.py | 2 +- .../analysis/symbolic/effect.py | 0 .../analysis/symbolic/guard.py | 0 .../analysis/symbolic/hook/__init__.py | 2 +- .../analysis/symbolic/hook/sidewinder_hook.py | 2 +- .../analysis/symbolic/lattice/__init__.py | 0 .../analysis/symbolic/lattice/final_string.py | 4 +++ .../symbolic/lattice/placeholder_string.py | 0 .../symbolic/runtime/effect/__init__.py | 0 .../symbolic/runtime/effect/effect_type.py | 0 .../runtime/effect/external_effect.py | 2 +- .../symbolic/runtime/effect/guarded_effect.py | 2 +- .../symbolic/runtime/memory/call_stack.py | 0 .../symbolic/runtime/memory/globals.py | 0 .../analysis/symbolic/runtime/memory/heap.py | 0 .../analysis/symbolic/runtime/memory/state.py | 0 .../symbolic/runtime/values/effector.py | 2 +- .../symbolic/runtime/values/heap_object.py | 11 +++++++ .../analysis/symbolic/runtime/values/key.py | 0 .../symbolic/runtime/values/primitive.py | 0 .../symbolic/runtime/values/symbolic_type.py | 0 .../symbolic/runtime/values/symbolic_value.py | 14 ++++++++ .../analysis/symbolic/value.py | 0 .../analysis/transform/__init__.py | 0 .../analysis/transform/errors.py | 0 .../analysis/transform/tests.txt | 0 .../analysis/transform/transformer.py | 32 +++++++++---------- .../analysis/transform/transformer_assign.py | 2 +- .../analysis/transform/transformer_base.py | 2 +- .../analysis/transform/transformer_classes.py | 2 +- .../analysis/transform/transformer_context.py | 0 .../transformer_control_flow_breaker.py | 4 +-- .../analysis/transform/transformer_expr.py | 6 ++-- .../analysis/transform/transformer_for.py | 2 +- .../transform/transformer_functions.py | 2 +- .../analysis/transform/transformer_helpers.py | 8 ++--- .../analysis/transform/transformer_if.py | 4 +-- .../analysis/transform/transformer_modules.py | 2 +- .../analysis/transform/transformer_try.py | 6 ++-- .../analysis/transform/transformer_while.py | 4 +-- .../analysis/transform/transformer_with.py | 2 +- .../analysis/transformer/rules.py | 0 .../analysis/transformer/version.py | 0 src/{ => sidewinder}/driver.py | 2 +- test/analysis/transform/test_transform.py | 2 +- 55 files changed, 97 insertions(+), 95 deletions(-) delete mode 100644 src/analysis/symbolic/lattice/final_string.py delete mode 100644 src/analysis/symbolic/runtime/values/heap_object.py delete mode 100644 src/analysis/symbolic/runtime/values/symbolic_value.py rename src/{ => sidewinder}/analysis/__init__.py (58%) rename src/{ => sidewinder}/analysis/analyzer.py (99%) rename src/{ => sidewinder}/analysis/symbolic/__init__.py (100%) rename src/{ => sidewinder}/analysis/symbolic/assumption.py (100%) rename src/{ => sidewinder}/analysis/symbolic/callable_value.py (50%) rename src/{ => sidewinder}/analysis/symbolic/effect.py (100%) rename src/{ => sidewinder}/analysis/symbolic/guard.py (100%) rename src/{ => sidewinder}/analysis/symbolic/hook/__init__.py (57%) rename src/{ => sidewinder}/analysis/symbolic/hook/sidewinder_hook.py (95%) rename src/{ => sidewinder}/analysis/symbolic/lattice/__init__.py (100%) create mode 100644 src/sidewinder/analysis/symbolic/lattice/final_string.py rename src/{ => sidewinder}/analysis/symbolic/lattice/placeholder_string.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/effect/__init__.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/effect/effect_type.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/effect/external_effect.py (76%) rename src/{ => sidewinder}/analysis/symbolic/runtime/effect/guarded_effect.py (65%) rename src/{ => sidewinder}/analysis/symbolic/runtime/memory/call_stack.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/memory/globals.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/memory/heap.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/memory/state.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/values/effector.py (71%) create mode 100644 src/sidewinder/analysis/symbolic/runtime/values/heap_object.py rename src/{ => sidewinder}/analysis/symbolic/runtime/values/key.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/values/primitive.py (100%) rename src/{ => sidewinder}/analysis/symbolic/runtime/values/symbolic_type.py (100%) create mode 100644 src/sidewinder/analysis/symbolic/runtime/values/symbolic_value.py rename src/{ => sidewinder}/analysis/symbolic/value.py (100%) rename src/{ => sidewinder}/analysis/transform/__init__.py (100%) rename src/{ => sidewinder}/analysis/transform/errors.py (100%) rename src/{ => sidewinder}/analysis/transform/tests.txt (100%) rename src/{ => sidewinder}/analysis/transform/transformer.py (86%) rename src/{ => sidewinder}/analysis/transform/transformer_assign.py (96%) rename src/{ => sidewinder}/analysis/transform/transformer_base.py (93%) rename src/{ => sidewinder}/analysis/transform/transformer_classes.py (90%) rename src/{ => sidewinder}/analysis/transform/transformer_context.py (100%) rename src/{ => sidewinder}/analysis/transform/transformer_control_flow_breaker.py (94%) rename src/{ => sidewinder}/analysis/transform/transformer_expr.py (98%) rename src/{ => sidewinder}/analysis/transform/transformer_for.py (97%) rename src/{ => sidewinder}/analysis/transform/transformer_functions.py (96%) rename src/{ => sidewinder}/analysis/transform/transformer_helpers.py (96%) rename src/{ => sidewinder}/analysis/transform/transformer_if.py (92%) rename src/{ => sidewinder}/analysis/transform/transformer_modules.py (88%) rename src/{ => sidewinder}/analysis/transform/transformer_try.py (96%) rename src/{ => sidewinder}/analysis/transform/transformer_while.py (95%) rename src/{ => sidewinder}/analysis/transform/transformer_with.py (97%) rename src/{ => sidewinder}/analysis/transformer/rules.py (100%) rename src/{ => sidewinder}/analysis/transformer/version.py (100%) rename src/{ => sidewinder}/driver.py (94%) diff --git a/.python-version b/.python-version index c8cfe39..e4fba21 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10 +3.12 diff --git a/.vscode/launch.json b/.vscode/launch.json index 6d6caa6..8a07dc8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,11 +5,9 @@ "name": "Python Debugger: Transformer", "type": "debugpy", "request": "launch", - "module": "src.analysis.transform.transformer", + "module": "sidewinder.analysis.transform.transformer", "console": "integratedTerminal", - "env": { - "PYTHONPATH": "${workspaceFolder}/src" - } + "justMyCode": false }, { "name": "Python Debugger: Tests", @@ -17,13 +15,10 @@ "request": "launch", "module": "pytest", "args": [ - "test/", - "-v" + "test/" ], "console": "integratedTerminal", - "env": { - "PYTHONPATH": "${workspaceFolder}/src" - } + "justMyCode": false } ] } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b7c3892..1146c73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,14 @@ +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + [project] -name = "chugubandar" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -requires-python = ">=3.10" -dependencies = [] +name = "sidewinder" +version = "0.0.1" +requires-python = ">=3.12" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] \ No newline at end of file diff --git a/src/analysis/symbolic/lattice/final_string.py b/src/analysis/symbolic/lattice/final_string.py deleted file mode 100644 index 6b7dbe8..0000000 --- a/src/analysis/symbolic/lattice/final_string.py +++ /dev/null @@ -1,4 +0,0 @@ -from symbolic.lattice.placeholder_string import SidewinderPlaceholderString - -class SidewinderFinalString(SidewinderPlaceholderString): - pass \ No newline at end of file diff --git a/src/analysis/symbolic/runtime/values/heap_object.py b/src/analysis/symbolic/runtime/values/heap_object.py deleted file mode 100644 index f7d99e5..0000000 --- a/src/analysis/symbolic/runtime/values/heap_object.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from analysis.symbolic.runtime.values.key import Key -from analysis.symbolic.runtime.values.symbolic_type import SymbolicType -from analysis.symbolic.runtime.values.symbolic_value import SymbolicValue - - -class HeapObject: - id: int - heap_map: dict[Key, SymbolicValue] - type: SymbolicType \ No newline at end of file diff --git a/src/analysis/symbolic/runtime/values/symbolic_value.py b/src/analysis/symbolic/runtime/values/symbolic_value.py deleted file mode 100644 index 0ef1464..0000000 --- a/src/analysis/symbolic/runtime/values/symbolic_value.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import annotations - -from typing import TypeAlias - -from analysis.symbolic.runtime.values.effector import Effector -from analysis.symbolic.runtime.values.heap_object import HeapObject -from analysis.symbolic.runtime.values.primitive import Primitive - - -SymbolicValue: TypeAlias = ( - Primitive - | HeapObject - | Effector -) \ No newline at end of file diff --git a/src/analysis/__init__.py b/src/sidewinder/analysis/__init__.py similarity index 58% rename from src/analysis/__init__.py rename to src/sidewinder/analysis/__init__.py index 9f1d6f3..09d31eb 100644 --- a/src/analysis/__init__.py +++ b/src/sidewinder/analysis/__init__.py @@ -4,8 +4,8 @@ This package provides comprehensive Python code analysis capabilities. """ -from analysis.analyzer import PythonCodeAnalyzer -from analysis.transform.transformer import SidewinderTransformer +from sidewinder.analysis.analyzer import PythonCodeAnalyzer +from sidewinder.analysis.transform.transformer import SidewinderTransformer __all__ = ['PythonCodeAnalyzer', 'SidewinderTransformer'] __version__ = '1.0.0' \ No newline at end of file diff --git a/src/analysis/analyzer.py b/src/sidewinder/analysis/analyzer.py similarity index 99% rename from src/analysis/analyzer.py rename to src/sidewinder/analysis/analyzer.py index 396f248..4a29e5c 100644 --- a/src/analysis/analyzer.py +++ b/src/sidewinder/analysis/analyzer.py @@ -7,7 +7,7 @@ import ast from typing import Dict, List, Any, Optional -from analysis.transform.transformer import SidewinderTransformer +from sidewinder.analysis.transform.transformer import SidewinderTransformer class PythonCodeAnalyzer: diff --git a/src/analysis/symbolic/__init__.py b/src/sidewinder/analysis/symbolic/__init__.py similarity index 100% rename from src/analysis/symbolic/__init__.py rename to src/sidewinder/analysis/symbolic/__init__.py diff --git a/src/analysis/symbolic/assumption.py b/src/sidewinder/analysis/symbolic/assumption.py similarity index 100% rename from src/analysis/symbolic/assumption.py rename to src/sidewinder/analysis/symbolic/assumption.py diff --git a/src/analysis/symbolic/callable_value.py b/src/sidewinder/analysis/symbolic/callable_value.py similarity index 50% rename from src/analysis/symbolic/callable_value.py rename to src/sidewinder/analysis/symbolic/callable_value.py index 94c01c6..151ca53 100644 --- a/src/analysis/symbolic/callable_value.py +++ b/src/sidewinder/analysis/symbolic/callable_value.py @@ -1,4 +1,4 @@ -from symbolic.value import SidewinderSymbolicValue +from sidewinder.analysis.symbolic.value import SidewinderSymbolicValue class SidewinderSymbolicCallableValue(SidewinderSymbolicValue): pass \ No newline at end of file diff --git a/src/analysis/symbolic/effect.py b/src/sidewinder/analysis/symbolic/effect.py similarity index 100% rename from src/analysis/symbolic/effect.py rename to src/sidewinder/analysis/symbolic/effect.py diff --git a/src/analysis/symbolic/guard.py b/src/sidewinder/analysis/symbolic/guard.py similarity index 100% rename from src/analysis/symbolic/guard.py rename to src/sidewinder/analysis/symbolic/guard.py diff --git a/src/analysis/symbolic/hook/__init__.py b/src/sidewinder/analysis/symbolic/hook/__init__.py similarity index 57% rename from src/analysis/symbolic/hook/__init__.py rename to src/sidewinder/analysis/symbolic/hook/__init__.py index 4fc8f74..4956b7f 100644 --- a/src/analysis/symbolic/hook/__init__.py +++ b/src/sidewinder/analysis/symbolic/hook/__init__.py @@ -1,4 +1,4 @@ -from analysis.symbolic.hook.sidewinder_hook import SidewinderHook, SidewinderHookNames +from sidewinder.analysis.symbolic.hook.sidewinder_hook import SidewinderHook, SidewinderHookNames from typing import Dict diff --git a/src/analysis/symbolic/hook/sidewinder_hook.py b/src/sidewinder/analysis/symbolic/hook/sidewinder_hook.py similarity index 95% rename from src/analysis/symbolic/hook/sidewinder_hook.py rename to src/sidewinder/analysis/symbolic/hook/sidewinder_hook.py index 2d842d6..7b1b210 100644 --- a/src/analysis/symbolic/hook/sidewinder_hook.py +++ b/src/sidewinder/analysis/symbolic/hook/sidewinder_hook.py @@ -1,7 +1,7 @@ from enum import Enum, auto from typing import Any, Protocol -from analysis.symbolic.runtime.memory.state import SidewinderState +from sidewinder.analysis.symbolic.runtime.memory.state import SidewinderState class SidewinderHookCallable(Protocol): diff --git a/src/analysis/symbolic/lattice/__init__.py b/src/sidewinder/analysis/symbolic/lattice/__init__.py similarity index 100% rename from src/analysis/symbolic/lattice/__init__.py rename to src/sidewinder/analysis/symbolic/lattice/__init__.py diff --git a/src/sidewinder/analysis/symbolic/lattice/final_string.py b/src/sidewinder/analysis/symbolic/lattice/final_string.py new file mode 100644 index 0000000..cc2d9b5 --- /dev/null +++ b/src/sidewinder/analysis/symbolic/lattice/final_string.py @@ -0,0 +1,4 @@ +from sidewinder.analysis.symbolic.lattice.placeholder_string import SidewinderPlaceholderString + +class SidewinderFinalString(SidewinderPlaceholderString): + pass \ No newline at end of file diff --git a/src/analysis/symbolic/lattice/placeholder_string.py b/src/sidewinder/analysis/symbolic/lattice/placeholder_string.py similarity index 100% rename from src/analysis/symbolic/lattice/placeholder_string.py rename to src/sidewinder/analysis/symbolic/lattice/placeholder_string.py diff --git a/src/analysis/symbolic/runtime/effect/__init__.py b/src/sidewinder/analysis/symbolic/runtime/effect/__init__.py similarity index 100% rename from src/analysis/symbolic/runtime/effect/__init__.py rename to src/sidewinder/analysis/symbolic/runtime/effect/__init__.py diff --git a/src/analysis/symbolic/runtime/effect/effect_type.py b/src/sidewinder/analysis/symbolic/runtime/effect/effect_type.py similarity index 100% rename from src/analysis/symbolic/runtime/effect/effect_type.py rename to src/sidewinder/analysis/symbolic/runtime/effect/effect_type.py diff --git a/src/analysis/symbolic/runtime/effect/external_effect.py b/src/sidewinder/analysis/symbolic/runtime/effect/external_effect.py similarity index 76% rename from src/analysis/symbolic/runtime/effect/external_effect.py rename to src/sidewinder/analysis/symbolic/runtime/effect/external_effect.py index cf47e9f..cdbc186 100644 --- a/src/analysis/symbolic/runtime/effect/external_effect.py +++ b/src/sidewinder/analysis/symbolic/runtime/effect/external_effect.py @@ -1,5 +1,5 @@ from analysis.symbolic.runtime.state.effect.effect_type import EffectType -from analysis.symbolic.runtime.values.symbolic_value import SymbolicValue +from sidewinder.analysis.symbolic.runtime.values.symbolic_value import SymbolicValue from enum import Enum, auto class OperationType(Enum): diff --git a/src/analysis/symbolic/runtime/effect/guarded_effect.py b/src/sidewinder/analysis/symbolic/runtime/effect/guarded_effect.py similarity index 65% rename from src/analysis/symbolic/runtime/effect/guarded_effect.py rename to src/sidewinder/analysis/symbolic/runtime/effect/guarded_effect.py index e45e0f0..8917f34 100644 --- a/src/analysis/symbolic/runtime/effect/guarded_effect.py +++ b/src/sidewinder/analysis/symbolic/runtime/effect/guarded_effect.py @@ -1,7 +1,7 @@ from __future__ import annotations from analysis.symbolic.runtime.state.effect import Effect -from analysis.symbolic.runtime.values.symbolic_value import SymbolicValue +from sidewinder.analysis.symbolic.runtime.values.symbolic_value import SymbolicValue class GuardedEffect: diff --git a/src/analysis/symbolic/runtime/memory/call_stack.py b/src/sidewinder/analysis/symbolic/runtime/memory/call_stack.py similarity index 100% rename from src/analysis/symbolic/runtime/memory/call_stack.py rename to src/sidewinder/analysis/symbolic/runtime/memory/call_stack.py diff --git a/src/analysis/symbolic/runtime/memory/globals.py b/src/sidewinder/analysis/symbolic/runtime/memory/globals.py similarity index 100% rename from src/analysis/symbolic/runtime/memory/globals.py rename to src/sidewinder/analysis/symbolic/runtime/memory/globals.py diff --git a/src/analysis/symbolic/runtime/memory/heap.py b/src/sidewinder/analysis/symbolic/runtime/memory/heap.py similarity index 100% rename from src/analysis/symbolic/runtime/memory/heap.py rename to src/sidewinder/analysis/symbolic/runtime/memory/heap.py diff --git a/src/analysis/symbolic/runtime/memory/state.py b/src/sidewinder/analysis/symbolic/runtime/memory/state.py similarity index 100% rename from src/analysis/symbolic/runtime/memory/state.py rename to src/sidewinder/analysis/symbolic/runtime/memory/state.py diff --git a/src/analysis/symbolic/runtime/values/effector.py b/src/sidewinder/analysis/symbolic/runtime/values/effector.py similarity index 71% rename from src/analysis/symbolic/runtime/values/effector.py rename to src/sidewinder/analysis/symbolic/runtime/values/effector.py index a38bf04..48fcc15 100644 --- a/src/analysis/symbolic/runtime/values/effector.py +++ b/src/sidewinder/analysis/symbolic/runtime/values/effector.py @@ -1,6 +1,6 @@ from typing import Protocol -from analysis.symbolic.runtime.values.symbolic_value import SymbolicValue +from sidewinder.analysis.symbolic.runtime.values.symbolic_value import SymbolicValue from analysis.symbolic.runtime.state.effect.guarded_effect import GuardedEffect diff --git a/src/sidewinder/analysis/symbolic/runtime/values/heap_object.py b/src/sidewinder/analysis/symbolic/runtime/values/heap_object.py new file mode 100644 index 0000000..f778cb8 --- /dev/null +++ b/src/sidewinder/analysis/symbolic/runtime/values/heap_object.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from sidewinder.analysis.symbolic.runtime.values.key import Key +from sidewinder.analysis.symbolic.runtime.values.symbolic_type import SymbolicType +from sidewinder.analysis.symbolic.runtime.values.symbolic_value import SymbolicValue + + +class HeapObject: + id: int + heap_map: dict[Key, SymbolicValue] + type: SymbolicType \ No newline at end of file diff --git a/src/analysis/symbolic/runtime/values/key.py b/src/sidewinder/analysis/symbolic/runtime/values/key.py similarity index 100% rename from src/analysis/symbolic/runtime/values/key.py rename to src/sidewinder/analysis/symbolic/runtime/values/key.py diff --git a/src/analysis/symbolic/runtime/values/primitive.py b/src/sidewinder/analysis/symbolic/runtime/values/primitive.py similarity index 100% rename from src/analysis/symbolic/runtime/values/primitive.py rename to src/sidewinder/analysis/symbolic/runtime/values/primitive.py diff --git a/src/analysis/symbolic/runtime/values/symbolic_type.py b/src/sidewinder/analysis/symbolic/runtime/values/symbolic_type.py similarity index 100% rename from src/analysis/symbolic/runtime/values/symbolic_type.py rename to src/sidewinder/analysis/symbolic/runtime/values/symbolic_type.py diff --git a/src/sidewinder/analysis/symbolic/runtime/values/symbolic_value.py b/src/sidewinder/analysis/symbolic/runtime/values/symbolic_value.py new file mode 100644 index 0000000..78aa771 --- /dev/null +++ b/src/sidewinder/analysis/symbolic/runtime/values/symbolic_value.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing import TypeAlias + +from sidewinder.analysis.symbolic.runtime.values.effector import Effector +from sidewinder.analysis.symbolic.runtime.values.heap_object import HeapObject +from sidewinder.analysis.symbolic.runtime.values.primitive import Primitive + + +SymbolicValue: TypeAlias = ( + Primitive + | HeapObject + | Effector +) \ No newline at end of file diff --git a/src/analysis/symbolic/value.py b/src/sidewinder/analysis/symbolic/value.py similarity index 100% rename from src/analysis/symbolic/value.py rename to src/sidewinder/analysis/symbolic/value.py diff --git a/src/analysis/transform/__init__.py b/src/sidewinder/analysis/transform/__init__.py similarity index 100% rename from src/analysis/transform/__init__.py rename to src/sidewinder/analysis/transform/__init__.py diff --git a/src/analysis/transform/errors.py b/src/sidewinder/analysis/transform/errors.py similarity index 100% rename from src/analysis/transform/errors.py rename to src/sidewinder/analysis/transform/errors.py diff --git a/src/analysis/transform/tests.txt b/src/sidewinder/analysis/transform/tests.txt similarity index 100% rename from src/analysis/transform/tests.txt rename to src/sidewinder/analysis/transform/tests.txt diff --git a/src/analysis/transform/transformer.py b/src/sidewinder/analysis/transform/transformer.py similarity index 86% rename from src/analysis/transform/transformer.py rename to src/sidewinder/analysis/transform/transformer.py index 6f75d8b..c4d39e8 100644 --- a/src/analysis/transform/transformer.py +++ b/src/sidewinder/analysis/transform/transformer.py @@ -11,22 +11,22 @@ import ast from typing import Dict, List, Any, Union, overload -from analysis.transform.errors import SidewinderIllegalStateError -from analysis.transform.transformer_assign import SidewinderAssignTransformerMixin -from analysis.transform.transformer_base import SidewinderTransformerBase, T -from analysis.transform.transformer_classes import SidewinderClassTransformerMixin -from analysis.transform.transformer_context import TransformerContext -from analysis.transform.transformer_control_flow_breaker import SidewinderControlFlowBreakerTransformerMixin -from analysis.transform.transformer_expr import SidewinderExprTransformerMixin -from analysis.transform.transformer_for import SidewinderForTransformerMixin -from analysis.transform.transformer_functions import SidewinderFunctionTransformerMixin -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers -from analysis.transform.transformer_if import SidewinderIfTransformerMixin -from analysis.transform.transformer_modules import SidewinderModuleTransformerMixin -from analysis.transform.transformer_try import SidewinderTryTransformerMixin -from analysis.transform.transformer_while import SidewinderWhileTransformerMixin -from analysis.transform.transformer_with import SidewinderWithTransformerMixin -from analysis.symbolic.hook import SidewinderHookNames +from sidewinder.analysis.transform.errors import SidewinderIllegalStateError +from sidewinder.analysis.transform.transformer_assign import SidewinderAssignTransformerMixin +from sidewinder.analysis.transform.transformer_base import SidewinderTransformerBase, T +from sidewinder.analysis.transform.transformer_classes import SidewinderClassTransformerMixin +from sidewinder.analysis.transform.transformer_context import TransformerContext +from sidewinder.analysis.transform.transformer_control_flow_breaker import SidewinderControlFlowBreakerTransformerMixin +from sidewinder.analysis.transform.transformer_expr import SidewinderExprTransformerMixin +from sidewinder.analysis.transform.transformer_for import SidewinderForTransformerMixin +from sidewinder.analysis.transform.transformer_functions import SidewinderFunctionTransformerMixin +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.transformer_if import SidewinderIfTransformerMixin +from sidewinder.analysis.transform.transformer_modules import SidewinderModuleTransformerMixin +from sidewinder.analysis.transform.transformer_try import SidewinderTryTransformerMixin +from sidewinder.analysis.transform.transformer_while import SidewinderWhileTransformerMixin +from sidewinder.analysis.transform.transformer_with import SidewinderWithTransformerMixin +from sidewinder.analysis.symbolic.hook import SidewinderHookNames # This is not the transformer which is behind LLMs. This is an AST transformer, which transforms the abstract syntax trees generated by parsing diff --git a/src/analysis/transform/transformer_assign.py b/src/sidewinder/analysis/transform/transformer_assign.py similarity index 96% rename from src/analysis/transform/transformer_assign.py rename to src/sidewinder/analysis/transform/transformer_assign.py index 554cbab..c0b09b4 100644 --- a/src/analysis/transform/transformer_assign.py +++ b/src/sidewinder/analysis/transform/transformer_assign.py @@ -1,7 +1,7 @@ import ast import copy -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderAssignTransformerMixin(SidewinderTransformerHelpers): diff --git a/src/analysis/transform/transformer_base.py b/src/sidewinder/analysis/transform/transformer_base.py similarity index 93% rename from src/analysis/transform/transformer_base.py rename to src/sidewinder/analysis/transform/transformer_base.py index b9c7aee..e8a4450 100644 --- a/src/analysis/transform/transformer_base.py +++ b/src/sidewinder/analysis/transform/transformer_base.py @@ -1,7 +1,7 @@ import ast from typing import TypeVar, TypeAlias, overload, Union, Any -from analysis.transform.transformer_context import TransformerContext +from sidewinder.analysis.transform.transformer_context import TransformerContext T = TypeVar('T', bound=ast.expr) diff --git a/src/analysis/transform/transformer_classes.py b/src/sidewinder/analysis/transform/transformer_classes.py similarity index 90% rename from src/analysis/transform/transformer_classes.py rename to src/sidewinder/analysis/transform/transformer_classes.py index b026a4e..3cc7fa7 100644 --- a/src/analysis/transform/transformer_classes.py +++ b/src/sidewinder/analysis/transform/transformer_classes.py @@ -1,7 +1,7 @@ import ast from typing import Any -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderClassTransformerMixin(SidewinderTransformerHelpers): def visit_ClassDef(self, node: ast.ClassDef) -> Any: diff --git a/src/analysis/transform/transformer_context.py b/src/sidewinder/analysis/transform/transformer_context.py similarity index 100% rename from src/analysis/transform/transformer_context.py rename to src/sidewinder/analysis/transform/transformer_context.py diff --git a/src/analysis/transform/transformer_control_flow_breaker.py b/src/sidewinder/analysis/transform/transformer_control_flow_breaker.py similarity index 94% rename from src/analysis/transform/transformer_control_flow_breaker.py rename to src/sidewinder/analysis/transform/transformer_control_flow_breaker.py index 0d2b739..668e9bd 100644 --- a/src/analysis/transform/transformer_control_flow_breaker.py +++ b/src/sidewinder/analysis/transform/transformer_control_flow_breaker.py @@ -1,8 +1,8 @@ import ast from typing import Any, Union -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers -from analysis.symbolic.hook import SidewinderHookNames +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.symbolic.hook import SidewinderHookNames class SidewinderControlFlowBreakerTransformerMixin(SidewinderTransformerHelpers): def visit_Return(self, node: ast.Return) -> Any: diff --git a/src/analysis/transform/transformer_expr.py b/src/sidewinder/analysis/transform/transformer_expr.py similarity index 98% rename from src/analysis/transform/transformer_expr.py rename to src/sidewinder/analysis/transform/transformer_expr.py index c43a10e..ecdee21 100644 --- a/src/analysis/transform/transformer_expr.py +++ b/src/sidewinder/analysis/transform/transformer_expr.py @@ -1,9 +1,9 @@ import ast from typing import Any -from analysis.transform.errors import SidewinderIllegalStateError -from analysis.symbolic.hook import SidewinderHookNames -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.errors import SidewinderIllegalStateError +from sidewinder.analysis.symbolic.hook import SidewinderHookNames +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderExprTransformerMixin(SidewinderTransformerHelpers): def visit_BinOp(self, node: ast.BinOp) -> tuple[list[ast.stmt], ast.expr]: diff --git a/src/analysis/transform/transformer_for.py b/src/sidewinder/analysis/transform/transformer_for.py similarity index 97% rename from src/analysis/transform/transformer_for.py rename to src/sidewinder/analysis/transform/transformer_for.py index 7169a94..bf65200 100644 --- a/src/analysis/transform/transformer_for.py +++ b/src/sidewinder/analysis/transform/transformer_for.py @@ -1,6 +1,6 @@ import ast -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderForTransformerMixin(SidewinderTransformerHelpers): def visit_For(self, node: ast.For) -> list[ast.stmt]: diff --git a/src/analysis/transform/transformer_functions.py b/src/sidewinder/analysis/transform/transformer_functions.py similarity index 96% rename from src/analysis/transform/transformer_functions.py rename to src/sidewinder/analysis/transform/transformer_functions.py index e92eff1..6b38ed1 100644 --- a/src/analysis/transform/transformer_functions.py +++ b/src/sidewinder/analysis/transform/transformer_functions.py @@ -1,7 +1,7 @@ import ast from typing import Any, Union -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderFunctionTransformerMixin(SidewinderTransformerHelpers): # ========== Function (sync and async) Def Nodes ========== diff --git a/src/analysis/transform/transformer_helpers.py b/src/sidewinder/analysis/transform/transformer_helpers.py similarity index 96% rename from src/analysis/transform/transformer_helpers.py rename to src/sidewinder/analysis/transform/transformer_helpers.py index ad1d049..4690127 100644 --- a/src/analysis/transform/transformer_helpers.py +++ b/src/sidewinder/analysis/transform/transformer_helpers.py @@ -3,10 +3,10 @@ from collections import defaultdict import copy -from analysis.transform.transformer_base import SidewinderTransformerBase, T -from analysis.transform.transformer_context import TransformerContext -from analysis.transform.errors import SidewinderIllegalStateError -from analysis.symbolic.hook import SidewinderHookNames +from sidewinder.analysis.transform.transformer_base import SidewinderTransformerBase, T +from sidewinder.analysis.transform.transformer_context import TransformerContext +from sidewinder.analysis.transform.errors import SidewinderIllegalStateError +from sidewinder.analysis.symbolic.hook import SidewinderHookNames class SidewinderTransformerHelpers(SidewinderTransformerBase): diff --git a/src/analysis/transform/transformer_if.py b/src/sidewinder/analysis/transform/transformer_if.py similarity index 92% rename from src/analysis/transform/transformer_if.py rename to src/sidewinder/analysis/transform/transformer_if.py index 543ff4b..ba0a858 100644 --- a/src/analysis/transform/transformer_if.py +++ b/src/sidewinder/analysis/transform/transformer_if.py @@ -1,7 +1,7 @@ import ast -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers -from analysis.symbolic.hook import SidewinderHookNames +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.symbolic.hook import SidewinderHookNames class SidewinderIfTransformerMixin(SidewinderTransformerHelpers): def visit_If(self, node: ast.If) -> list[ast.stmt]: diff --git a/src/analysis/transform/transformer_modules.py b/src/sidewinder/analysis/transform/transformer_modules.py similarity index 88% rename from src/analysis/transform/transformer_modules.py rename to src/sidewinder/analysis/transform/transformer_modules.py index d9ec3f6..7d46aaf 100644 --- a/src/analysis/transform/transformer_modules.py +++ b/src/sidewinder/analysis/transform/transformer_modules.py @@ -1,7 +1,7 @@ import ast from typing import Any -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderModuleTransformerMixin(SidewinderTransformerHelpers): def visit_Module(self, node: ast.Module) -> Any: diff --git a/src/analysis/transform/transformer_try.py b/src/sidewinder/analysis/transform/transformer_try.py similarity index 96% rename from src/analysis/transform/transformer_try.py rename to src/sidewinder/analysis/transform/transformer_try.py index a943aa8..49fbd65 100644 --- a/src/analysis/transform/transformer_try.py +++ b/src/sidewinder/analysis/transform/transformer_try.py @@ -1,8 +1,8 @@ import ast -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers -from analysis.transform.errors import SidewinderIllegalStateError -from analysis.symbolic.hook import SidewinderHookNames +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.errors import SidewinderIllegalStateError +from sidewinder.analysis.symbolic.hook import SidewinderHookNames from typing import Any diff --git a/src/analysis/transform/transformer_while.py b/src/sidewinder/analysis/transform/transformer_while.py similarity index 95% rename from src/analysis/transform/transformer_while.py rename to src/sidewinder/analysis/transform/transformer_while.py index 5a46e46..3b10a7c 100644 --- a/src/analysis/transform/transformer_while.py +++ b/src/sidewinder/analysis/transform/transformer_while.py @@ -1,7 +1,7 @@ import ast -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers -from analysis.symbolic.hook import SidewinderHookNames +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.symbolic.hook import SidewinderHookNames class SidewinderWhileTransformerMixin(SidewinderTransformerHelpers): def visit_While(self, node: ast.While) -> list[ast.stmt]: diff --git a/src/analysis/transform/transformer_with.py b/src/sidewinder/analysis/transform/transformer_with.py similarity index 97% rename from src/analysis/transform/transformer_with.py rename to src/sidewinder/analysis/transform/transformer_with.py index 834e8fb..0766857 100644 --- a/src/analysis/transform/transformer_with.py +++ b/src/sidewinder/analysis/transform/transformer_with.py @@ -1,6 +1,6 @@ import ast -from analysis.transform.transformer_helpers import SidewinderTransformerHelpers +from sidewinder.analysis.transform.transformer_helpers import SidewinderTransformerHelpers class SidewinderWithTransformerMixin(SidewinderTransformerHelpers): def visit_With(self, node: ast.With) -> list[ast.stmt]: diff --git a/src/analysis/transformer/rules.py b/src/sidewinder/analysis/transformer/rules.py similarity index 100% rename from src/analysis/transformer/rules.py rename to src/sidewinder/analysis/transformer/rules.py diff --git a/src/analysis/transformer/version.py b/src/sidewinder/analysis/transformer/version.py similarity index 100% rename from src/analysis/transformer/version.py rename to src/sidewinder/analysis/transformer/version.py diff --git a/src/driver.py b/src/sidewinder/driver.py similarity index 94% rename from src/driver.py rename to src/sidewinder/driver.py index cfdaa84..e86b747 100644 --- a/src/driver.py +++ b/src/sidewinder/driver.py @@ -1,7 +1,7 @@ import sys import os from pathlib import Path -from analysis.analyzer import PythonCodeAnalyzer +from sidewinder.analysis.analyzer import PythonCodeAnalyzer def main(): diff --git a/test/analysis/transform/test_transform.py b/test/analysis/transform/test_transform.py index ececb31..0a7df60 100644 --- a/test/analysis/transform/test_transform.py +++ b/test/analysis/transform/test_transform.py @@ -4,7 +4,7 @@ import pytest import warnings from pathlib import Path -from analysis.transform.transformer import SidewinderTransformer # adjust import path +from sidewinder.analysis.transform.transformer import SidewinderTransformer # adjust import path INPUTS_DIR = Path(__file__).parent / "inputs" OUTPUTS_DIR = Path(__file__).parent / "outputs" From 47407a6dbbfeefaa80824db0bd17b1fb7973b91b Mon Sep 17 00:00:00 2001 From: aayan636 Date: Mon, 6 Jul 2026 07:34:19 -0700 Subject: [PATCH 5/7] Github Actions for tests --- .github/workflows/tests.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..50c511f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,32 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.12"] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run tests + run: | + python -m pytest -q \ No newline at end of file From 985bff98ff5e71fbd30ccbb539d4c2d69e5a65eb Mon Sep 17 00:00:00 2001 From: aayan636 Date: Mon, 6 Jul 2026 07:38:18 -0700 Subject: [PATCH 6/7] debugging workflow --- .github/workflows/tests.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 50c511f..a54cdc8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,7 @@ on: push: branches: [main] pull_request: + branches: [main] jobs: test: @@ -14,7 +15,7 @@ jobs: python-version: ["3.12"] steps: - - name: Checkout code + - name: Checkout repository uses: actions/checkout@v4 - name: Set up Python @@ -22,11 +23,19 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Upgrade pip + run: python -m pip install --upgrade pip + + # Install your project + dev dependencies (pytest lives here) - name: Install dependencies run: | - python -m pip install --upgrade pip python -m pip install -e ".[dev]" - - name: Run tests + # Sanity check (helps debug future CI issues) + - name: Verify environment run: | - python -m pytest -q \ No newline at end of file + python -c "import sidewinder; print('sidewinder import OK')" + python -m pip show pytest + + - name: Run tests + run: python -m pytest -q \ No newline at end of file From 47d58f718aebee63710f13f51e8519bdd163bd61 Mon Sep 17 00:00:00 2001 From: aayan636 Date: Mon, 6 Jul 2026 07:40:41 -0700 Subject: [PATCH 7/7] attempting fix --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1146c73..e873b7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,11 @@ name = "sidewinder" version = "0.0.1" requires-python = ">=3.12" +[project.optional-dependencies] +dev = [ + "pytest>=8", +] + [tool.setuptools] package-dir = {"" = "src"}