From f2230dfee3ab19974bfe818afdca7526bc81280e Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Mon, 25 Nov 2024 15:46:26 -0500 Subject: [PATCH 01/28] changes for check_differential_testing_function --- src/inline/inline.py | 16 ++++++ src/inline/plugin.py | 127 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/src/inline/inline.py b/src/inline/inline.py index 5766542..18606f0 100644 --- a/src/inline/inline.py +++ b/src/inline/inline.py @@ -10,6 +10,7 @@ def __init__( tag: List = [], disabled: bool = False, timeout: float = -1.0, + devices: List[str] = None, ): """ Initialize Inline object with test name / parametrized flag @@ -20,6 +21,8 @@ def __init__( :param tag: tags to group tests :param disabled: whether the test is disabled :param timeout: seconds to timeout the test, must be a float + :param devices: list of devices to run differential testing on (e.g., ["cpu", "cuda", "mps"]) + if None, differential testing is disabled """ def given(self, variable, value): @@ -42,6 +45,19 @@ def check_eq(self, actual_value, expected_value): :raises: AssertionError """ return self + + def check_differential_testing(self, outputs): + """ + Assert whether outputs are consistent across different devices. + This method compares the outputs from different devices specified in the constructor. + + :param outputs: a dictionary mapping device names to their outputs, or a single output value + if a single value is provided, the test will run the computation on all devices + and compare against this reference value + :returns: Inline object + :raises: AssertionError if outputs differ across devices + """ + return self def check_neq(self, actual_value, expected_value): """ diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 11c0774..60faf3b 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -171,6 +171,7 @@ def __init__(self): self.tag = [] self.disabled = False self.timeout = -1.0 + self.devices = None self.globs = {} def to_test(self): @@ -293,6 +294,8 @@ class ExtractInlineTest(ast.NodeTransformer): arg_tag_str = "tag" arg_disabled_str = "disabled" arg_timeout_str = "timeout" + arg_devices_str = "devices" + check_differential_testing_str = "check_differential_testing" assume = "assume" inline_module_imported = False @@ -362,7 +365,7 @@ def parse_constructor(self, node): """ Parse a constructor call. """ - NUM_OF_ARGUMENTS = 6 + NUM_OF_ARGUMENTS = 7 if len(node.args) + len(node.keywords) <= NUM_OF_ARGUMENTS: # positional arguments if sys.version_info >= (3, 8, 0): @@ -394,6 +397,17 @@ def parse_constructor(self, node): and (isinstance(arg.value, float) or isinstance(arg.value, int)) ): self.cur_inline_test.timeout = arg.value + + elif index == 6 and isinstance(arg, ast.List): + devices = [] + for elt in arg.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException("devices can only be List of string") + if elt.value not in {"cpu", "cuda", "mps"}: + raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") + devices.append(elt.value) + self.cur_inline_test.devices = devices + else: raise MalformedException( f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" @@ -431,6 +445,16 @@ def parse_constructor(self, node): raise MalformedException(f"tag can only be List of string") tags.append(elt.value) self.cur_inline_test.tag = tags + # Add devices handling for keyword args + elif keyword.arg == self.arg_devices_str and isinstance(keyword.value, ast.List): + devices = [] + for elt in keyword.value.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException("devices can only be List of string") + if elt.value not in {"cpu", "cuda", "mps"}: + raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") + devices.append(elt.value) + self.cur_inline_test.devices = devices # check if "disabled" is a boolean elif ( keyword.arg == self.arg_disabled_str @@ -447,6 +471,16 @@ def parse_constructor(self, node): if keyword.value.value <= 0.0: raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") self.cur_inline_test.timeout = keyword.value.value + # Add devices handling for Python 3.7 + elif index == 6 and isinstance(arg, ast.List): + devices = [] + for elt in arg.elts: + if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): # Note: ast.Str for Python 3.7 + raise MalformedException("devices can only be List of string") + if elt.s not in {"cpu", "cuda", "mps"}: + raise MalformedException(f"Invalid device: {elt.s}. Must be one of ['cpu', 'cuda', 'mps']") + devices.append(elt.s) + self.cur_inline_test.devices = devices else: raise MalformedException( f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" @@ -536,6 +570,19 @@ def parse_constructor(self, node): if keyword.value.n <= 0.0: raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") self.cur_inline_test.timeout = keyword.value.n + #keyword arg for devices + elif ( + keyword.arg == self.arg_devices_str + and isinstance(keyword.value, ast.List) + ): + devices = [] + for elt in keyword.value.elts: + if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): + raise MalformedException("devices can only be List of string") + if elt.s not in {"cpu", "cuda", "mps"}: + raise MalformedException(f"Invalid device: {elt.s}. Must be one of ['cpu', 'cuda', 'mps']") + devices.append(elt.s) + self.cur_inline_test.devices = devices else: raise MalformedException( f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" @@ -885,6 +932,80 @@ def parse_check_not_same(self, node): self.cur_inline_test.check_stmts.append(assert_node) else: raise MalformedException("inline test: invalid check_not_same(), expected 2 args") + + def parse_check_differential_testing(self, node): + + if self.cur_inline_test.devices is None: + raise MalformedException("check_differential_testing can only be used when devices parameter is provided") + + if len(node.args) == 1: + output_node = self.parse_group(node.args[0]) + + if self.cur_inline_test.parameterized: + self.parameterized_inline_tests_init(node.args[0]) + for index, _ in enumerate(node.args[0].elts): + # Compare outputs between consecutive devices + for i in range(len(self.cur_inline_test.devices)-1): + device1 = self.cur_inline_test.devices[i] + device2 = self.cur_inline_test.devices[i+1] + assert_node = self.build_assert_eq( + ast.Call( + func=ast.Attribute( + value=ast.Call( + func=ast.Attribute(value=output_node, attr='to'), + args=[ast.Constant(value=device1)], + keywords=[] + ), + attr='allclose' + ), + args=[ + ast.Call( + func=ast.Attribute(value=output_node, attr='to'), + args=[ast.Constant(value=device2)], + keywords=[] + ) + ], + keywords=[ + ast.keyword(arg='rtol', value=ast.Constant(value=1e-5)), + ast.keyword(arg='atol', value=ast.Constant(value=1e-5)) + ] + ), + ast.Constant(value=True) + ) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) + else: + # Non-parameterized case + for i in range(len(self.cur_inline_test.devices)-1): + device1 = self.cur_inline_test.devices[i] + device2 = self.cur_inline_test.devices[i+1] + assert_node = self.build_assert_eq( + ast.Call( + func=ast.Attribute( + value=ast.Call( + func=ast.Attribute(value=output_node, attr='to'), + args=[ast.Constant(value=device1)], + keywords=[] + ), + attr='allclose' + ), + args=[ + ast.Call( + func=ast.Attribute(value=output_node, attr='to'), + args=[ast.Constant(value=device2)], + keywords=[] + ) + ], + keywords=[ + ast.keyword(arg='rtol', value=ast.Constant(value=1e-5)), + ast.keyword(arg='atol', value=ast.Constant(value=1e-5)) + ] + ), + ast.Constant(value=True) + ) + self.cur_inline_test.check_stmts.append(assert_node) + else: + raise MalformedException("check_differential_testing() accepts exactly 1 argument") + def build_fail(self): equal_node = ast.Compare( @@ -986,11 +1107,13 @@ def parse_inline_test(self, node): self.parse_check_same(call) elif call.func.attr == self.check_not_same: self.parse_check_not_same(call) + elif call.func.attr == self.check_differential_testing_str: + self.parse_check_differential_testing(call) elif call.func.attr == self.fail_str: self.parse_fail(call) elif call.func.attr == self.given_str: raise MalformedException( - f"inline test: given() must be called before check_eq()/check_true()/check_false()" + f"inline test: given() must be called before check_eq()/check_true()/check_false()/check_differential_testing()" ) else: raise MalformedException(f"inline test: invalid function call {self.node_to_source_code(call.func)}") From 54373f2d330198636632274c9018010b5b1e1d5e Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Sun, 15 Dec 2024 18:57:14 -0500 Subject: [PATCH 02/28] working changes for the check_diff() --- src/inline/inline.py | 2 +- src/inline/plugin.py | 156 ++++++++++++++++++++++++------------------- 2 files changed, 88 insertions(+), 70 deletions(-) diff --git a/src/inline/inline.py b/src/inline/inline.py index 18606f0..9673ce8 100644 --- a/src/inline/inline.py +++ b/src/inline/inline.py @@ -10,7 +10,7 @@ def __init__( tag: List = [], disabled: bool = False, timeout: float = -1.0, - devices: List[str] = None, + devices: List = None, ): """ Initialize Inline object with test name / parametrized flag diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 60faf3b..24715ab 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -935,77 +935,93 @@ def parse_check_not_same(self, node): def parse_check_differential_testing(self, node): - if self.cur_inline_test.devices is None: - raise MalformedException("check_differential_testing can only be used when devices parameter is provided") - - if len(node.args) == 1: - output_node = self.parse_group(node.args[0]) + if not self.cur_inline_test.devices: + raise MalformedException("check_differential_testing can only be used with the 'devices' parameter.") + + if len(node.args) != 1: + raise MalformedException("check_differential_testing() requires exactly 1 argument.") + + # Parse the tensor operation + output_node = self.parse_group(node.args[0]) + + # Get the original operation from the previous statements + original_op = None + for stmt in self.cur_inline_test.previous_stmts: + if isinstance(stmt, ast.Assign) and stmt.targets[0].id == output_node.id: + original_op = stmt.value + break + + if not original_op: + raise MalformedException("Could not find original operation for differential testing") + + device_statements = [] + device_outputs = [] + + # Generate device-specific tensors and operations + for device in self.cur_inline_test.devices: + # Create device-specific input tensor + input_var = self.cur_inline_test.given_stmts[0].targets[0].id + device_input_var = f"{input_var}_{device}" + device_input_stmt = ast.Assign( + targets=[ast.Name(id=device_input_var, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=self.cur_inline_test.given_stmts[0].value, + attr="to" + ), + args=[ast.Constant(value=device)], + keywords=[] + ) + ) + device_statements.append(device_input_stmt) + + # Create device-specific operation result + device_output_var = f"output_{device}" + # Copy the original operation but replace the input tensor with device-specific one + device_op = copy.deepcopy(original_op) + # Replace the input tensor reference in the operation + for node in ast.walk(device_op): + if isinstance(node, ast.Name) and node.id == input_var: + node.id = device_input_var + + device_output_stmt = ast.Assign( + targets=[ast.Name(id=device_output_var, ctx=ast.Store())], + value=device_op + ) + device_statements.append(device_output_stmt) + device_outputs.append(device_output_var) + + # Add the comparison across devices + comparisons = [] + for i in range(len(device_outputs) - 1): + device1_var = device_outputs[i] + device2_var = device_outputs[i + 1] - if self.cur_inline_test.parameterized: - self.parameterized_inline_tests_init(node.args[0]) - for index, _ in enumerate(node.args[0].elts): - # Compare outputs between consecutive devices - for i in range(len(self.cur_inline_test.devices)-1): - device1 = self.cur_inline_test.devices[i] - device2 = self.cur_inline_test.devices[i+1] - assert_node = self.build_assert_eq( - ast.Call( - func=ast.Attribute( - value=ast.Call( - func=ast.Attribute(value=output_node, attr='to'), - args=[ast.Constant(value=device1)], - keywords=[] - ), - attr='allclose' - ), - args=[ - ast.Call( - func=ast.Attribute(value=output_node, attr='to'), - args=[ast.Constant(value=device2)], - keywords=[] - ) - ], - keywords=[ - ast.keyword(arg='rtol', value=ast.Constant(value=1e-5)), - ast.keyword(arg='atol', value=ast.Constant(value=1e-5)) - ] - ), - ast.Constant(value=True) - ) - self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) - else: - # Non-parameterized case - for i in range(len(self.cur_inline_test.devices)-1): - device1 = self.cur_inline_test.devices[i] - device2 = self.cur_inline_test.devices[i+1] - assert_node = self.build_assert_eq( - ast.Call( - func=ast.Attribute( - value=ast.Call( - func=ast.Attribute(value=output_node, attr='to'), - args=[ast.Constant(value=device1)], - keywords=[] - ), - attr='allclose' - ), - args=[ - ast.Call( - func=ast.Attribute(value=output_node, attr='to'), - args=[ast.Constant(value=device2)], - keywords=[] - ) - ], - keywords=[ - ast.keyword(arg='rtol', value=ast.Constant(value=1e-5)), - ast.keyword(arg='atol', value=ast.Constant(value=1e-5)) - ] - ), - ast.Constant(value=True) - ) - self.cur_inline_test.check_stmts.append(assert_node) - else: - raise MalformedException("check_differential_testing() accepts exactly 1 argument") + comparison = self.build_assert_eq( + ast.Call( + func=ast.Attribute( + value=ast.Name(id=device1_var, ctx=ast.Load()), + attr="allclose" + ), + args=[ + ast.Name(id=device2_var, ctx=ast.Load()), + ], + keywords=[ + ast.keyword(arg="rtol", value=ast.Constant(value=1e-5)), + ast.keyword(arg="atol", value=ast.Constant(value=1e-5)) + ] + ), + ast.Constant(value=True) + ) + comparisons.append(comparison) + # Update the inline test object + self.cur_inline_test.previous_stmts.extend(device_statements) + self.cur_inline_test.check_stmts.extend(comparisons) + + + + def build_fail(self): equal_node = ast.Compare( @@ -1254,6 +1270,8 @@ def _find(self, tests, obj, module, globs, seen): ###################################################################### class InlineTestRunner: def run(self, test: InlineTest, out: List) -> None: + test_str = test.to_test() + print(test_str) tree = ast.parse(test.to_test()) codeobj = compile(tree, filename="", mode="exec") start_time = time.time() From 4b1ae6b6a58367268f12e06c2dab11bafa814cc6 Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Sun, 15 Dec 2024 19:42:23 -0500 Subject: [PATCH 03/28] changes to fix runtime error due to tensors on different devices --- src/inline/plugin.py | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 24715ab..42c72bb 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -992,19 +992,54 @@ def parse_check_differential_testing(self, node): device_outputs.append(device_output_var) # Add the comparison across devices + # Always convert tensors to CPU for comparison comparisons = [] for i in range(len(device_outputs) - 1): device1_var = device_outputs[i] device2_var = device_outputs[i + 1] + # Create CPU versions of the outputs for comparison + device1_cpu_var = f"{device1_var}_cpu_compare" + device2_cpu_var = f"{device2_var}_cpu_compare" + + # Add statements to move tensors to CPU + device_statements.append( + ast.Assign( + targets=[ast.Name(id=device1_cpu_var, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=device1_var, ctx=ast.Load()), + attr="to" + ), + args=[ast.Constant(value="cpu")], + keywords=[] + ) + ) + ) + + device_statements.append( + ast.Assign( + targets=[ast.Name(id=device2_cpu_var, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=device2_var, ctx=ast.Load()), + attr="to" + ), + args=[ast.Constant(value="cpu")], + keywords=[] + ) + ) + ) + + # Compare the CPU versions comparison = self.build_assert_eq( ast.Call( func=ast.Attribute( - value=ast.Name(id=device1_var, ctx=ast.Load()), + value=ast.Name(id=device1_cpu_var, ctx=ast.Load()), attr="allclose" ), args=[ - ast.Name(id=device2_var, ctx=ast.Load()), + ast.Name(id=device2_cpu_var, ctx=ast.Load()), ], keywords=[ ast.keyword(arg="rtol", value=ast.Constant(value=1e-5)), @@ -1018,8 +1053,7 @@ def parse_check_differential_testing(self, node): # Update the inline test object self.cur_inline_test.previous_stmts.extend(device_statements) self.cur_inline_test.check_stmts.extend(comparisons) - - + From d3f89413ab762d8fe34fbe9c310ac0c5830c5e58 Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Sun, 9 Feb 2025 13:54:02 -0500 Subject: [PATCH 04/28] changes for the name of the differential testing function --- src/inline/inline.py | 2 +- src/inline/plugin.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/inline/inline.py b/src/inline/inline.py index 9673ce8..847c253 100644 --- a/src/inline/inline.py +++ b/src/inline/inline.py @@ -46,7 +46,7 @@ def check_eq(self, actual_value, expected_value): """ return self - def check_differential_testing(self, outputs): + def diff_test(self, outputs): """ Assert whether outputs are consistent across different devices. This method compares the outputs from different devices specified in the constructor. diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 42c72bb..ec0a6b9 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -295,7 +295,7 @@ class ExtractInlineTest(ast.NodeTransformer): arg_disabled_str = "disabled" arg_timeout_str = "timeout" arg_devices_str = "devices" - check_differential_testing_str = "check_differential_testing" + diff_test_str = "diff_test" assume = "assume" inline_module_imported = False @@ -933,13 +933,13 @@ def parse_check_not_same(self, node): else: raise MalformedException("inline test: invalid check_not_same(), expected 2 args") - def parse_check_differential_testing(self, node): + def parse_diff_test(self, node): if not self.cur_inline_test.devices: - raise MalformedException("check_differential_testing can only be used with the 'devices' parameter.") + raise MalformedException("diff_test() can only be used with the 'devices' parameter.") if len(node.args) != 1: - raise MalformedException("check_differential_testing() requires exactly 1 argument.") + raise MalformedException("diff_test() requires exactly 1 argument.") # Parse the tensor operation output_node = self.parse_group(node.args[0]) @@ -952,7 +952,7 @@ def parse_check_differential_testing(self, node): break if not original_op: - raise MalformedException("Could not find original operation for differential testing") + raise MalformedException("Could not find original operation for diff_test") device_statements = [] device_outputs = [] @@ -1157,13 +1157,13 @@ def parse_inline_test(self, node): self.parse_check_same(call) elif call.func.attr == self.check_not_same: self.parse_check_not_same(call) - elif call.func.attr == self.check_differential_testing_str: - self.parse_check_differential_testing(call) + elif call.func.attr == self.diff_test_str: + self.parse_diff_test(call) elif call.func.attr == self.fail_str: self.parse_fail(call) elif call.func.attr == self.given_str: raise MalformedException( - f"inline test: given() must be called before check_eq()/check_true()/check_false()/check_differential_testing()" + f"inline test: given() must be called before check_eq()/check_true()/check_false()/diff_test()" ) else: raise MalformedException(f"inline test: invalid function call {self.node_to_source_code(call.func)}") From 7dd09a1d0dc40a4f18352606eb89b226472dd0bd Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Sun, 9 Feb 2025 17:01:11 -0500 Subject: [PATCH 05/28] fixing the issue where the assertion failed if the randn() generated nan since nan!= nan. Adding it in the allclose() call --- src/inline/plugin.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index ec0a6b9..74860ee 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -934,9 +934,8 @@ def parse_check_not_same(self, node): raise MalformedException("inline test: invalid check_not_same(), expected 2 args") def parse_diff_test(self, node): - if not self.cur_inline_test.devices: - raise MalformedException("diff_test() can only be used with the 'devices' parameter.") + raise MalformedException("diff_test can only be used with the 'devices' parameter.") if len(node.args) != 1: raise MalformedException("diff_test() requires exactly 1 argument.") @@ -957,16 +956,25 @@ def parse_diff_test(self, node): device_statements = [] device_outputs = [] - # Generate device-specific tensors and operations + # Use the input tensor from the given statement + input_tensor = self.cur_inline_test.given_stmts[0].value # This is the actual input tensor + input_var = self.cur_inline_test.given_stmts[0].targets[0].id + + # Store original input tensor to avoid regeneration + input_store = ast.Assign( + targets=[ast.Name(id=input_var, ctx=ast.Store())], + value=input_tensor + ) + device_statements.append(input_store) + for device in self.cur_inline_test.devices: # Create device-specific input tensor - input_var = self.cur_inline_test.given_stmts[0].targets[0].id device_input_var = f"{input_var}_{device}" device_input_stmt = ast.Assign( targets=[ast.Name(id=device_input_var, ctx=ast.Store())], value=ast.Call( func=ast.Attribute( - value=self.cur_inline_test.given_stmts[0].value, + value=ast.Name(id=input_var, ctx=ast.Load()), # Use stored input tensor attr="to" ), args=[ast.Constant(value=device)], @@ -975,6 +983,8 @@ def parse_diff_test(self, node): ) device_statements.append(device_input_stmt) + # Rest of your code remains the same... + # Create device-specific operation result device_output_var = f"output_{device}" # Copy the original operation but replace the input tensor with device-specific one @@ -1043,7 +1053,8 @@ def parse_diff_test(self, node): ], keywords=[ ast.keyword(arg="rtol", value=ast.Constant(value=1e-5)), - ast.keyword(arg="atol", value=ast.Constant(value=1e-5)) + ast.keyword(arg="atol", value=ast.Constant(value=1e-5)), + ast.keyword(arg="equal_nan", value=ast.Constant(value=True)) ] ), ast.Constant(value=True) From ddd381e5b4a24840d0e20d4c97ecbb4ecea53b51 Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Wed, 19 Feb 2025 15:28:56 -0500 Subject: [PATCH 06/28] changes to solve the tensor on multiple devices for multiple inputs --- src/inline/plugin.py | 74 ++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 74860ee..4a5f464 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -940,10 +940,9 @@ def parse_diff_test(self, node): if len(node.args) != 1: raise MalformedException("diff_test() requires exactly 1 argument.") - # Parse the tensor operation output_node = self.parse_group(node.args[0]) - # Get the original operation from the previous statements + # Get the original operation original_op = None for stmt in self.cur_inline_test.previous_stmts: if isinstance(stmt, ast.Assign) and stmt.targets[0].id == output_node.id: @@ -956,43 +955,48 @@ def parse_diff_test(self, node): device_statements = [] device_outputs = [] - # Use the input tensor from the given statement - input_tensor = self.cur_inline_test.given_stmts[0].value # This is the actual input tensor - input_var = self.cur_inline_test.given_stmts[0].targets[0].id - - # Store original input tensor to avoid regeneration - input_store = ast.Assign( - targets=[ast.Name(id=input_var, ctx=ast.Store())], - value=input_tensor - ) - device_statements.append(input_store) - - for device in self.cur_inline_test.devices: - # Create device-specific input tensor - device_input_var = f"{input_var}_{device}" - device_input_stmt = ast.Assign( - targets=[ast.Name(id=device_input_var, ctx=ast.Store())], - value=ast.Call( - func=ast.Attribute( - value=ast.Name(id=input_var, ctx=ast.Load()), # Use stored input tensor - attr="to" - ), - args=[ast.Constant(value=device)], - keywords=[] + # Handle all input tensors from given statements + input_vars = [] + for given_stmt in self.cur_inline_test.given_stmts: + input_vars.append(given_stmt.targets[0].id) + # Store original input + device_statements.append( + ast.Assign( + targets=[ast.Name(id=given_stmt.targets[0].id, ctx=ast.Store())], + value=given_stmt.value ) ) - device_statements.append(device_input_stmt) - # Rest of your code remains the same... + # For each device, create device-specific versions of all input tensors + for device in self.cur_inline_test.devices: + device_input_vars = {} # Map original var names to device-specific ones + + # Move each input tensor to the current device + for input_var in input_vars: + device_input_var = f"{input_var}_{device}" + device_input_vars[input_var] = device_input_var + + device_input_stmt = ast.Assign( + targets=[ast.Name(id=device_input_var, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=input_var, ctx=ast.Load()), + attr="to" + ), + args=[ast.Constant(value=device)], + keywords=[] + ) + ) + device_statements.append(device_input_stmt) - # Create device-specific operation result + # Create device-specific operation device_output_var = f"output_{device}" - # Copy the original operation but replace the input tensor with device-specific one device_op = copy.deepcopy(original_op) - # Replace the input tensor reference in the operation + + # Replace all input tensor references with device-specific ones for node in ast.walk(device_op): - if isinstance(node, ast.Name) and node.id == input_var: - node.id = device_input_var + if isinstance(node, ast.Name) and node.id in device_input_vars: + node.id = device_input_vars[node.id] device_output_stmt = ast.Assign( targets=[ast.Name(id=device_output_var, ctx=ast.Store())], @@ -1001,18 +1005,16 @@ def parse_diff_test(self, node): device_statements.append(device_output_stmt) device_outputs.append(device_output_var) + # Rest of the comparison code remains the same... # Add the comparison across devices - # Always convert tensors to CPU for comparison comparisons = [] for i in range(len(device_outputs) - 1): device1_var = device_outputs[i] device2_var = device_outputs[i + 1] - # Create CPU versions of the outputs for comparison device1_cpu_var = f"{device1_var}_cpu_compare" device2_cpu_var = f"{device2_var}_cpu_compare" - # Add statements to move tensors to CPU device_statements.append( ast.Assign( targets=[ast.Name(id=device1_cpu_var, ctx=ast.Store())], @@ -1041,7 +1043,6 @@ def parse_diff_test(self, node): ) ) - # Compare the CPU versions comparison = self.build_assert_eq( ast.Call( func=ast.Attribute( @@ -1061,7 +1062,6 @@ def parse_diff_test(self, node): ) comparisons.append(comparison) - # Update the inline test object self.cur_inline_test.previous_stmts.extend(device_statements) self.cur_inline_test.check_stmts.extend(comparisons) From 56d7d45fef81d57372a5632daf21daab47db64b2 Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Mon, 10 Mar 2025 17:22:33 -0400 Subject: [PATCH 07/28] new changes for in place torch apis --- src/inline/plugin.py | 250 ++++++++++++++++++++++++++++--------------- 1 file changed, 162 insertions(+), 88 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 4a5f464..68db137 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -951,119 +951,193 @@ def parse_diff_test(self, node): if not original_op: raise MalformedException("Could not find original operation for diff_test") - - device_statements = [] + + # Check if the operation is in-place (ends with _) + is_inplace = False + op_name = "" + + if isinstance(original_op, ast.Call) and hasattr(original_op, "func"): + if isinstance(original_op.func, ast.Attribute): + op_name = original_op.func.attr + + # Check if operation name ends with '_' (in-place) + is_inplace = op_name.endswith('_') + + # Create our new statements + new_statements = [] device_outputs = [] - - # Handle all input tensors from given statements - input_vars = [] + + # Process input tensors for given_stmt in self.cur_inline_test.given_stmts: - input_vars.append(given_stmt.targets[0].id) - # Store original input - device_statements.append( - ast.Assign( - targets=[ast.Name(id=given_stmt.targets[0].id, ctx=ast.Store())], - value=given_stmt.value - ) - ) - - # For each device, create device-specific versions of all input tensors - for device in self.cur_inline_test.devices: - device_input_vars = {} # Map original var names to device-specific ones + input_var = given_stmt.targets[0].id + ref_var = f"{input_var}_ref" - # Move each input tensor to the current device - for input_var in input_vars: - device_input_var = f"{input_var}_{device}" - device_input_vars[input_var] = device_input_var - - device_input_stmt = ast.Assign( - targets=[ast.Name(id=device_input_var, ctx=ast.Store())], + # Always clone inputs for in-place operations + new_statements.append( + ast.Assign( + targets=[ast.Name(id=ref_var, ctx=ast.Store())], value=ast.Call( func=ast.Attribute( - value=ast.Name(id=input_var, ctx=ast.Load()), - attr="to" + value=given_stmt.value, + attr="clone" ), - args=[ast.Constant(value=device)], + args=[], keywords=[] ) ) - device_statements.append(device_input_stmt) - - # Create device-specific operation - device_output_var = f"output_{device}" + ) + + # Create device-specific versions + for device in self.cur_inline_test.devices: + device_var = f"{input_var}_{device}" + + new_statements.append( + ast.Assign( + targets=[ast.Name(id=device_var, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=ref_var, ctx=ast.Load()), + attr="to" + ), + args=[ast.Constant(value=device)], + keywords=[] + ) + ) + ) + + # Create device-specific operations + device_input_map = {device: {} for device in self.cur_inline_test.devices} + for device in self.cur_inline_test.devices: + for given_stmt in self.cur_inline_test.given_stmts: + input_var = given_stmt.targets[0].id + device_input_map[device][input_var] = f"{input_var}_{device}" + device_op = copy.deepcopy(original_op) - # Replace all input tensor references with device-specific ones - for node in ast.walk(device_op): - if isinstance(node, ast.Name) and node.id in device_input_vars: - node.id = device_input_vars[node.id] - - device_output_stmt = ast.Assign( - targets=[ast.Name(id=device_output_var, ctx=ast.Store())], - value=device_op - ) - device_statements.append(device_output_stmt) - device_outputs.append(device_output_var) - - # Rest of the comparison code remains the same... - # Add the comparison across devices - comparisons = [] - for i in range(len(device_outputs) - 1): - device1_var = device_outputs[i] - device2_var = device_outputs[i + 1] + # Replace input references + class ReplaceInputs(ast.NodeTransformer): + def visit_Name(self, node): + if node.id in device_input_map[device]: + return ast.Name(id=device_input_map[device][node.id], ctx=node.ctx) + return node - device1_cpu_var = f"{device1_var}_cpu_compare" - device2_cpu_var = f"{device2_var}_cpu_compare" + device_op = ReplaceInputs().visit(device_op) + device_output = f"output_{device}" - device_statements.append( + new_statements.append( ast.Assign( - targets=[ast.Name(id=device1_cpu_var, ctx=ast.Store())], - value=ast.Call( + targets=[ast.Name(id=device_output, ctx=ast.Store())], + value=device_op + ) + ) + device_outputs.append(device_output) + + # Choose appropriate comparison method + comparisons = [] + if is_inplace and ('random' in op_name.lower() or 'exponential' in op_name.lower() or + 'normal' in op_name.lower() or 'uniform' in op_name.lower()): + # For in-place stochastic operations, check basic properties + for device_output in device_outputs: + # 1. Check that output has values (not all zeros) + has_values_check = self.build_assert_true( + ast.Call( func=ast.Attribute( - value=ast.Name(id=device1_var, ctx=ast.Load()), - attr="to" + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=device_output, ctx=ast.Load()), + attr="abs" + ), + args=[], + keywords=[] + ), + attr="sum" ), - args=[ast.Constant(value="cpu")], + args=[], keywords=[] ) ) - ) - - device_statements.append( - ast.Assign( - targets=[ast.Name(id=device2_cpu_var, ctx=ast.Store())], - value=ast.Call( + comparisons.append(has_values_check) + + # 2. Check that output has finite values (not inf) + has_finite_check = self.build_assert_true( + ast.Call( func=ast.Attribute( - value=ast.Name(id=device2_var, ctx=ast.Load()), - attr="to" + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id="torch", ctx=ast.Load()), + attr="isfinite" + ), + args=[ast.Name(id=device_output, ctx=ast.Load())], + keywords=[] + ), + attr="all" ), - args=[ast.Constant(value="cpu")], + args=[], keywords=[] ) ) - ) - - comparison = self.build_assert_eq( - ast.Call( - func=ast.Attribute( - value=ast.Name(id=device1_cpu_var, ctx=ast.Load()), - attr="allclose" + comparisons.append(has_finite_check) + else: + # For deterministic operations, use allclose comparison + for i in range(len(device_outputs) - 1): + dev1 = device_outputs[i] + dev2 = device_outputs[i + 1] + + dev1_cpu = f"{dev1}_cpu" + dev2_cpu = f"{dev2}_cpu" + + # Move outputs back to CPU for comparison + new_statements.append( + ast.Assign( + targets=[ast.Name(id=dev1_cpu, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=dev1, ctx=ast.Load()), + attr="to" + ), + args=[ast.Constant(value="cpu")], + keywords=[] + ) + ) + ) + + new_statements.append( + ast.Assign( + targets=[ast.Name(id=dev2_cpu, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=dev2, ctx=ast.Load()), + attr="to" + ), + args=[ast.Constant(value="cpu")], + keywords=[] + ) + ) + ) + + # Standard allclose comparison + comparison = self.build_assert_eq( + ast.Call( + func=ast.Attribute( + value=ast.Name(id=dev1_cpu, ctx=ast.Load()), + attr="allclose" + ), + args=[ + ast.Name(id=dev2_cpu, ctx=ast.Load()) + ], + keywords=[ + ast.keyword(arg="rtol", value=ast.Constant(value=1e-4)), + ast.keyword(arg="atol", value=ast.Constant(value=1e-4)), + ast.keyword(arg="equal_nan", value=ast.Constant(value=True)) + ] ), - args=[ - ast.Name(id=device2_cpu_var, ctx=ast.Load()), - ], - keywords=[ - ast.keyword(arg="rtol", value=ast.Constant(value=1e-5)), - ast.keyword(arg="atol", value=ast.Constant(value=1e-5)), - ast.keyword(arg="equal_nan", value=ast.Constant(value=True)) - ] - ), - ast.Constant(value=True) - ) - comparisons.append(comparison) - - self.cur_inline_test.previous_stmts.extend(device_statements) - self.cur_inline_test.check_stmts.extend(comparisons) + ast.Constant(value=True) + ) + comparisons.append(comparison) + + # Replace statements + self.cur_inline_test.previous_stmts = new_statements + self.cur_inline_test.check_stmts = comparisons From dc12691a8800261a985413aaac58d453ea588311 Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Tue, 20 May 2025 00:39:02 +0530 Subject: [PATCH 08/28] random sampling issue --- src/inline/plugin.py | 178 +++++++++++++++++++++++++++++++++---------- 1 file changed, 138 insertions(+), 40 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 68db137..66e3179 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -952,21 +952,93 @@ def parse_diff_test(self, node): if not original_op: raise MalformedException("Could not find original operation for diff_test") - # Check if the operation is in-place (ends with _) - is_inplace = False + # Check if the operation is stochastic + is_stochastic = False op_name = "" if isinstance(original_op, ast.Call) and hasattr(original_op, "func"): if isinstance(original_op.func, ast.Attribute): op_name = original_op.func.attr - # Check if operation name ends with '_' (in-place) - is_inplace = op_name.endswith('_') + # Check if operation name indicates a stochastic operation + stochastic_keywords = ['sample', 'random', 'exponential', 'normal', 'uniform', 'multinomial', 'dropout'] + is_stochastic = any(keyword in op_name.lower() for keyword in stochastic_keywords) # Create our new statements new_statements = [] device_outputs = [] + # Import necessary modules for seed setting + if is_stochastic: + # Import needed modules + import_random = ast.ImportFrom( + module='random', + names=[ast.alias(name='seed', asname=None)], + level=0 + ) + new_statements.append(import_random) + + import_np = ast.ImportFrom( + module='numpy', + names=[ast.alias(name='random', asname='np_random')], + level=0 + ) + new_statements.append(import_np) + + # Create seed function + seed_func_def = ast.FunctionDef( + name='set_random_seed', + args=ast.arguments( + posonlyargs=[], + args=[ast.arg(arg='seed_value', annotation=None)], + kwonlyargs=[], + kw_defaults=[], + defaults=[] + ), + body=[ + ast.Expr( + value=ast.Call( + func=ast.Name(id='seed', ctx=ast.Load()), + args=[ast.Name(id='seed_value', ctx=ast.Load())], + keywords=[] + ) + ), + ast.Expr( + value=ast.Call( + func=ast.Attribute( + value=ast.Attribute( + value=ast.Name(id='torch', ctx=ast.Load()), + attr='manual_seed', + ctx=ast.Load() + ), + attr='__call__', + ctx=ast.Load() + ), + args=[ast.Name(id='seed_value', ctx=ast.Load())], + keywords=[] + ) + ), + ast.Expr( + value=ast.Call( + func=ast.Attribute( + value=ast.Attribute( + value=ast.Name(id='np_random', ctx=ast.Load()), + attr='seed', + ctx=ast.Load() + ), + attr='__call__', + ctx=ast.Load() + ), + args=[ast.Name(id='seed_value', ctx=ast.Load())], + keywords=[] + ) + ) + ], + decorator_list=[], + returns=None + ) + new_statements.append(seed_func_def) + # Process input tensors for given_stmt in self.cur_inline_test.given_stmts: input_var = given_stmt.targets[0].id @@ -1012,6 +1084,18 @@ def parse_diff_test(self, node): input_var = given_stmt.targets[0].id device_input_map[device][input_var] = f"{input_var}_{device}" + # Set same seed before each device operation if stochastic + if is_stochastic: + new_statements.append( + ast.Expr( + value=ast.Call( + func=ast.Name(id='set_random_seed', ctx=ast.Load()), + args=[ast.Constant(value=42)], # Use constant seed 42 + keywords=[] + ) + ) + ) + device_op = copy.deepcopy(original_op) # Replace input references @@ -1034,51 +1118,65 @@ def visit_Name(self, node): # Choose appropriate comparison method comparisons = [] - if is_inplace and ('random' in op_name.lower() or 'exponential' in op_name.lower() or - 'normal' in op_name.lower() or 'uniform' in op_name.lower()): - # For in-place stochastic operations, check basic properties - for device_output in device_outputs: - # 1. Check that output has values (not all zeros) - has_values_check = self.build_assert_true( - ast.Call( - func=ast.Attribute( - value=ast.Call( - func=ast.Attribute( - value=ast.Name(id=device_output, ctx=ast.Load()), - attr="abs" - ), - args=[], - keywords=[] + if is_stochastic: + # For stochastic operations, use standard comparison + for i in range(len(device_outputs) - 1): + dev1 = device_outputs[i] + dev2 = device_outputs[i + 1] + + dev1_cpu = f"{dev1}_cpu" + dev2_cpu = f"{dev2}_cpu" + + # Move outputs back to CPU for comparison + new_statements.append( + ast.Assign( + targets=[ast.Name(id=dev1_cpu, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=dev1, ctx=ast.Load()), + attr="to" ), - attr="sum" - ), - args=[], - keywords=[] + args=[ast.Constant(value="cpu")], + keywords=[] + ) ) ) - comparisons.append(has_values_check) - # 2. Check that output has finite values (not inf) - has_finite_check = self.build_assert_true( + new_statements.append( + ast.Assign( + targets=[ast.Name(id=dev2_cpu, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=dev2, ctx=ast.Load()), + attr="to" + ), + args=[ast.Constant(value="cpu")], + keywords=[] + ) + ) + ) + + # Standard allclose comparison + comparison = self.build_assert_eq( ast.Call( func=ast.Attribute( - value=ast.Call( - func=ast.Attribute( - value=ast.Name(id="torch", ctx=ast.Load()), - attr="isfinite" - ), - args=[ast.Name(id=device_output, ctx=ast.Load())], - keywords=[] - ), - attr="all" + value=ast.Name(id=dev1_cpu, ctx=ast.Load()), + attr="allclose" ), - args=[], - keywords=[] - ) + args=[ + ast.Name(id=dev2_cpu, ctx=ast.Load()) + ], + keywords=[ + ast.keyword(arg="rtol", value=ast.Constant(value=1e-4)), + ast.keyword(arg="atol", value=ast.Constant(value=1e-4)), + ast.keyword(arg="equal_nan", value=ast.Constant(value=True)) + ] + ), + ast.Constant(value=True) ) - comparisons.append(has_finite_check) + comparisons.append(comparison) else: - # For deterministic operations, use allclose comparison + # For deterministic operations, use standard comparison for i in range(len(device_outputs) - 1): dev1 = device_outputs[i] dev2 = device_outputs[i + 1] From 661c9527a41936e6b0e5588c269531b7e2a3e1d0 Mon Sep 17 00:00:00 2001 From: Chaitanya Shahane Date: Thu, 22 May 2025 00:12:28 +0530 Subject: [PATCH 09/28] new update to apply changes to tackle random operations for all --- src/inline/plugin.py | 312 ++++++++++++++++--------------------------- 1 file changed, 115 insertions(+), 197 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 66e3179..13f3025 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -952,92 +952,70 @@ def parse_diff_test(self, node): if not original_op: raise MalformedException("Could not find original operation for diff_test") - # Check if the operation is stochastic - is_stochastic = False - op_name = "" - - if isinstance(original_op, ast.Call) and hasattr(original_op, "func"): - if isinstance(original_op.func, ast.Attribute): - op_name = original_op.func.attr - - # Check if operation name indicates a stochastic operation - stochastic_keywords = ['sample', 'random', 'exponential', 'normal', 'uniform', 'multinomial', 'dropout'] - is_stochastic = any(keyword in op_name.lower() for keyword in stochastic_keywords) - # Create our new statements new_statements = [] device_outputs = [] - # Import necessary modules for seed setting - if is_stochastic: - # Import needed modules - import_random = ast.ImportFrom( - module='random', - names=[ast.alias(name='seed', asname=None)], - level=0 - ) - new_statements.append(import_random) - - import_np = ast.ImportFrom( - module='numpy', - names=[ast.alias(name='random', asname='np_random')], - level=0 - ) - new_statements.append(import_np) - - # Create seed function - seed_func_def = ast.FunctionDef( - name='set_random_seed', - args=ast.arguments( - posonlyargs=[], - args=[ast.arg(arg='seed_value', annotation=None)], - kwonlyargs=[], - kw_defaults=[], - defaults=[] + # Import necessary modules for seed setting - Always add these + # Import random module + import_random = ast.ImportFrom( + module='random', + names=[ast.alias(name='seed', asname=None)], + level=0 + ) + new_statements.append(import_random) + + # Import numpy.random + import_np = ast.ImportFrom( + module='numpy', + names=[ast.alias(name='random', asname='np_random')], + level=0 + ) + new_statements.append(import_np) + + # Create seed function - Always add this + seed_func_def = ast.FunctionDef( + name='set_random_seed', + args=ast.arguments( + posonlyargs=[], + args=[ast.arg(arg='seed_value', annotation=None)], + kwonlyargs=[], + kw_defaults=[], + defaults=[] + ), + body=[ + ast.Expr( + value=ast.Call( + func=ast.Name(id='seed', ctx=ast.Load()), + args=[ast.Name(id='seed_value', ctx=ast.Load())], + keywords=[] + ) ), - body=[ - ast.Expr( - value=ast.Call( - func=ast.Name(id='seed', ctx=ast.Load()), - args=[ast.Name(id='seed_value', ctx=ast.Load())], - keywords=[] - ) - ), - ast.Expr( - value=ast.Call( - func=ast.Attribute( - value=ast.Attribute( - value=ast.Name(id='torch', ctx=ast.Load()), - attr='manual_seed', - ctx=ast.Load() - ), - attr='__call__', - ctx=ast.Load() - ), - args=[ast.Name(id='seed_value', ctx=ast.Load())], - keywords=[] - ) - ), - ast.Expr( - value=ast.Call( - func=ast.Attribute( - value=ast.Attribute( - value=ast.Name(id='np_random', ctx=ast.Load()), - attr='seed', - ctx=ast.Load() - ), - attr='__call__', - ctx=ast.Load() - ), - args=[ast.Name(id='seed_value', ctx=ast.Load())], - keywords=[] - ) + ast.Expr( + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id='torch', ctx=ast.Load()), + attr='manual_seed' + ), + args=[ast.Name(id='seed_value', ctx=ast.Load())], + keywords=[] ) - ], - decorator_list=[], - returns=None - ) - new_statements.append(seed_func_def) + ), + ast.Expr( + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id='np_random', ctx=ast.Load()), + attr='seed' + ), + args=[ast.Name(id='seed_value', ctx=ast.Load())], + keywords=[] + ) + ) + ], + decorator_list=[], + returns=None + ) + new_statements.append(seed_func_def) # Process input tensors for given_stmt in self.cur_inline_test.given_stmts: @@ -1084,17 +1062,16 @@ def parse_diff_test(self, node): input_var = given_stmt.targets[0].id device_input_map[device][input_var] = f"{input_var}_{device}" - # Set same seed before each device operation if stochastic - if is_stochastic: - new_statements.append( - ast.Expr( - value=ast.Call( - func=ast.Name(id='set_random_seed', ctx=ast.Load()), - args=[ast.Constant(value=42)], # Use constant seed 42 - keywords=[] - ) + # Always set seed before each device operation - no condition check + new_statements.append( + ast.Expr( + value=ast.Call( + func=ast.Name(id='set_random_seed', ctx=ast.Load()), + args=[ast.Constant(value=42)], # Use constant seed 42 + keywords=[] ) ) + ) device_op = copy.deepcopy(original_op) @@ -1116,122 +1093,63 @@ def visit_Name(self, node): ) device_outputs.append(device_output) - # Choose appropriate comparison method + # Standard comparison method for all operations - no condition check comparisons = [] - if is_stochastic: - # For stochastic operations, use standard comparison - for i in range(len(device_outputs) - 1): - dev1 = device_outputs[i] - dev2 = device_outputs[i + 1] - - dev1_cpu = f"{dev1}_cpu" - dev2_cpu = f"{dev2}_cpu" - - # Move outputs back to CPU for comparison - new_statements.append( - ast.Assign( - targets=[ast.Name(id=dev1_cpu, ctx=ast.Store())], - value=ast.Call( - func=ast.Attribute( - value=ast.Name(id=dev1, ctx=ast.Load()), - attr="to" - ), - args=[ast.Constant(value="cpu")], - keywords=[] - ) - ) - ) - - new_statements.append( - ast.Assign( - targets=[ast.Name(id=dev2_cpu, ctx=ast.Store())], - value=ast.Call( - func=ast.Attribute( - value=ast.Name(id=dev2, ctx=ast.Load()), - attr="to" - ), - args=[ast.Constant(value="cpu")], - keywords=[] - ) - ) - ) - - # Standard allclose comparison - comparison = self.build_assert_eq( - ast.Call( + for i in range(len(device_outputs) - 1): + dev1 = device_outputs[i] + dev2 = device_outputs[i + 1] + + dev1_cpu = f"{dev1}_cpu" + dev2_cpu = f"{dev2}_cpu" + + # Move outputs back to CPU for comparison + new_statements.append( + ast.Assign( + targets=[ast.Name(id=dev1_cpu, ctx=ast.Store())], + value=ast.Call( func=ast.Attribute( - value=ast.Name(id=dev1_cpu, ctx=ast.Load()), - attr="allclose" + value=ast.Name(id=dev1, ctx=ast.Load()), + attr="to" ), - args=[ - ast.Name(id=dev2_cpu, ctx=ast.Load()) - ], - keywords=[ - ast.keyword(arg="rtol", value=ast.Constant(value=1e-4)), - ast.keyword(arg="atol", value=ast.Constant(value=1e-4)), - ast.keyword(arg="equal_nan", value=ast.Constant(value=True)) - ] - ), - ast.Constant(value=True) - ) - comparisons.append(comparison) - else: - # For deterministic operations, use standard comparison - for i in range(len(device_outputs) - 1): - dev1 = device_outputs[i] - dev2 = device_outputs[i + 1] - - dev1_cpu = f"{dev1}_cpu" - dev2_cpu = f"{dev2}_cpu" - - # Move outputs back to CPU for comparison - new_statements.append( - ast.Assign( - targets=[ast.Name(id=dev1_cpu, ctx=ast.Store())], - value=ast.Call( - func=ast.Attribute( - value=ast.Name(id=dev1, ctx=ast.Load()), - attr="to" - ), - args=[ast.Constant(value="cpu")], - keywords=[] - ) - ) - ) - - new_statements.append( - ast.Assign( - targets=[ast.Name(id=dev2_cpu, ctx=ast.Store())], - value=ast.Call( - func=ast.Attribute( - value=ast.Name(id=dev2, ctx=ast.Load()), - attr="to" - ), - args=[ast.Constant(value="cpu")], - keywords=[] - ) + args=[ast.Constant(value="cpu")], + keywords=[] ) ) - - # Standard allclose comparison - comparison = self.build_assert_eq( - ast.Call( + ) + + new_statements.append( + ast.Assign( + targets=[ast.Name(id=dev2_cpu, ctx=ast.Store())], + value=ast.Call( func=ast.Attribute( - value=ast.Name(id=dev1_cpu, ctx=ast.Load()), - attr="allclose" + value=ast.Name(id=dev2, ctx=ast.Load()), + attr="to" ), - args=[ - ast.Name(id=dev2_cpu, ctx=ast.Load()) - ], - keywords=[ - ast.keyword(arg="rtol", value=ast.Constant(value=1e-4)), - ast.keyword(arg="atol", value=ast.Constant(value=1e-4)), - ast.keyword(arg="equal_nan", value=ast.Constant(value=True)) - ] - ), - ast.Constant(value=True) + args=[ast.Constant(value="cpu")], + keywords=[] + ) ) - comparisons.append(comparison) + ) + + # Standard allclose comparison + comparison = self.build_assert_eq( + ast.Call( + func=ast.Attribute( + value=ast.Name(id=dev1_cpu, ctx=ast.Load()), + attr="allclose" + ), + args=[ + ast.Name(id=dev2_cpu, ctx=ast.Load()) + ], + keywords=[ + ast.keyword(arg="rtol", value=ast.Constant(value=1e-4)), + ast.keyword(arg="atol", value=ast.Constant(value=1e-4)), + ast.keyword(arg="equal_nan", value=ast.Constant(value=True)) + ] + ), + ast.Constant(value=True) + ) + comparisons.append(comparison) # Replace statements self.cur_inline_test.previous_stmts = new_statements From ab77f5cd7fe78010acddc15352e7565ea9a8b7d3 Mon Sep 17 00:00:00 2001 From: hanse141 <54557962+hanse141@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:58:15 -0400 Subject: [PATCH 10/28] Constructor Comment for Argument Order --- src/inline/plugin.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 13f3025..e217a6b 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -365,6 +365,16 @@ def parse_constructor(self, node): """ Parse a constructor call. """ + + # Argument Order: + # 0) test_name (str) + # 1) parameterized (bool) + # 2) repeated (positive integer) + # 3) tag (str) + # 4) disabled (bool) + # 5) timeout (positive float) + # 6) devices (str array) + NUM_OF_ARGUMENTS = 7 if len(node.args) + len(node.keywords) <= NUM_OF_ARGUMENTS: # positional arguments @@ -590,6 +600,7 @@ def parse_constructor(self, node): else: raise MalformedException(f"inline test: invalid {self.class_name_str}(), expected at most 3 args") + if not self.cur_inline_test.test_name: # by default, use lineno as test name self.cur_inline_test.test_name = f"line{node.lineno}" From 5cc8b2a1221df57705f4fca27c2e2e8898f67602 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 31 Oct 2025 00:28:09 -0400 Subject: [PATCH 11/28] Analysis and Initial Ideas --- demo/example.py | 2 +- src/inline/plugin.py | 51 +++++++++++++++++++++++++++++++++++++++----- tests/test_plugin.py | 3 +++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/demo/example.py b/demo/example.py index 63f1894..c43a024 100644 --- a/demo/example.py +++ b/demo/example.py @@ -17,7 +17,7 @@ def get_assignment_map_from_checkpoint(tvars, init_checkpoint): # inline test itest().given(name, "a:0").check_eq(m.group(1), "a") # a failing inline test - # itest().given(name, "a:0").check_eq(m.group(1), "aaa") + # itest().given(name, "a:0").check_eq(m.group(1), "aaa") if m is not None: name = m.group(1) name_to_variable[name] = var diff --git a/src/inline/plugin.py b/src/inline/plugin.py index e217a6b..11e981e 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -252,7 +252,7 @@ class TimeoutException(Exception): ## InlineTest Parser ###################################################################### class InlinetestParser: - def parse(self, obj, globs: None): + def parse(self, obj, globs: None): # obj = open(self.file_path, "r").read(): if isinstance(obj, ModuleType): tree = ast.parse(open(obj.__file__, "r").read()) @@ -302,6 +302,7 @@ class ExtractInlineTest(ast.NodeTransformer): def __init__(self): self.cur_inline_test = InlineTest() self.inline_test_list = [] + self.import_list = [] def is_inline_test_class(self, node): if isinstance(node, ast.Call): @@ -361,6 +362,13 @@ def collect_inline_test_calls(self, node, inline_test_calls: List[ast.Call]): inline_test_calls.append(node) self.collect_inline_test_calls(node.func, inline_test_calls) + def collect_import_calls(self, node, import_calls: List[ast.FunctionDef]): + """ + collect all import calls in the node (should be done first) + """ + + pass + def parse_constructor(self, node): """ Parse a constructor call. @@ -1166,7 +1174,14 @@ def visit_Name(self, node): self.cur_inline_test.previous_stmts = new_statements self.cur_inline_test.check_stmts = comparisons - + def parse_import(self, node): + # Differentiate between import, from import, and import alias + import_node = ast.Import( + names=[ + ast.alias(name=node) + ] + ) + return import_node def build_fail(self): @@ -1208,6 +1223,7 @@ def parse_group(self, node): return stmt else: return node + def parse_parameterized_test(self): for index, parameterized_test in enumerate(self.cur_inline_test.parameterized_inline_tests): @@ -1217,8 +1233,13 @@ def parse_parameterized_test(self): parameterized_test.test_name = self.cur_inline_test.test_name + "_" + str(index) def parse_inline_test(self, node): + import_calls = [] inline_test_calls = [] + self.collect_inline_test_calls(node, inline_test_calls) + self.collect_import_calls(node, import_calls) + + #Why reverse? inline_test_calls.reverse() if len(inline_test_calls) <= 1: @@ -1309,6 +1330,7 @@ def node_to_source_code(node): ## InlineTest Finder ###################################################################### class InlineTestFinder: + # Finder should NOT store any global variables def __init__(self, parser=InlinetestParser(), recurse=True, exclude_empty=True): self._parser = parser self._recurse = recurse @@ -1353,7 +1375,14 @@ def _is_routine(self, obj): pass return inspect.isroutine(maybe_routine) - def find(self, obj, module=None, globs=None, extraglobs=None): + # def find_imports(self, obj, module=None): + # if module is False: + # module = None + # elif module is None: + # module = inspect.getmodule(obj) + + + def find(self, obj, module=None, globs=None, extraglobs=None, imports=None): # Find the module that contains the given object (if obj is # a module, then module=obj.). if module is False: @@ -1374,15 +1403,23 @@ def find(self, obj, module=None, globs=None, extraglobs=None): if "__name__" not in globs: globs["__name__"] = "__main__" # provide a default module name + # Find intersection between loaded modules and module imports + if imports is None: + imports = set(sys.modules) & set(globs) + else: + imports = imports.copy() + # Recursively explore `obj`, extracting InlineTests. tests = [] - self._find(tests, obj, module, globs, {}) + self._find(tests, obj, module, globs, imports, {}) return tests - def _find(self, tests, obj, module, globs, seen): + def _find(self, tests, obj, module, globs, imports, seen): if id(obj) in seen: return seen[id(obj)] = 1 + + # Find a test for this object, and add it to the list of tests. test = self._parser.parse(obj, globs) if test is not None: @@ -1555,6 +1592,10 @@ def collect(self) -> Iterable[InlinetestItem]: group_tags = self.config.getoption("inlinetest_group", default=None) order_tags = self.config.getoption("inlinetest_order", default=None) + # TODO: import all modules through the finder first before extracting inline tests + # - Create ast for all imports + # - If a function references an import, then include the imported library reference in the ast + for test_list in finder.find(module): # reorder the list if there are tests to be ordered ordered_list = InlinetestModule.order_tests(test_list, order_tags) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 40c3096..06d25f5 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -2,6 +2,9 @@ from _pytest.pytester import Pytester import pytest +if __name__ == "__main__": + pytest.main(['-v', '-s']) + # pytest -p pytester class TestInlinetests: From c79623d63fc24ee37283786de48f9941c14a6f09 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 31 Oct 2025 12:01:39 -0400 Subject: [PATCH 12/28] Even More Input Prototyping --- src/inline/plugin.py | 66 ++++++++++++++++++++++++++++++++------------ tests/test_plugin.py | 17 ++++++++++++ 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 11e981e..4cf1c76 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -159,6 +159,7 @@ def __init__(self): self.check_stmts = [] self.given_stmts = [] self.previous_stmts = [] + self.import_stmts = [] self.prev_stmt_type = PrevStmtType.StmtExpr # the line number of test statement self.lineno = 0 @@ -297,6 +298,11 @@ class ExtractInlineTest(ast.NodeTransformer): arg_devices_str = "devices" diff_test_str = "diff_test" assume = "assume" + + import_str = "import" + from_str = "from" + as_str = "as" + inline_module_imported = False def __init__(self): @@ -352,22 +358,38 @@ def find_previous_stmt(self, node): return prev_stmt_node return self.find_condition_stmt(prev_stmt_node) - def collect_inline_test_calls(self, node, inline_test_calls: List[ast.Call]): + def collect_inline_test_calls(self, node, inline_test_calls: List[ast.Call], import_calls: List[ast.Import], import_from_calls: List[ast.ImportFrom]): """ collect all function calls in the node """ if isinstance(node, ast.Attribute): - self.collect_inline_test_calls(node.value, inline_test_calls) + self.collect_inline_test_calls(node.value, inline_test_calls, import_calls, import_from_calls) elif isinstance(node, ast.Call): inline_test_calls.append(node) - self.collect_inline_test_calls(node.func, inline_test_calls) - - def collect_import_calls(self, node, import_calls: List[ast.FunctionDef]): + self.collect_inline_test_calls(node.func, inline_test_calls, import_calls, import_from_calls) + elif isinstance(node, ast.Import): + import_calls.append(node) + self.collect_inline_test_calls(node.func, inline_test_calls, import_calls, import_from_calls) + elif isinstance(node, ast.ImportFrom): + import_from_calls.append(node) + self.collect_inline_test_calls(node.func, inline_test_calls, import_calls, import_from_calls) + + def collect_import_calls(self, node, import_calls: List[ast.Import], import_from_calls: List[ast.ImportFrom]): """ collect all import calls in the node (should be done first) """ - - pass + + while not isinstance(node, ast.Module) and node.parent != None: + node = node.parent + + if not isinstance(node, ast.Module): + return + + for child in node.children: + if isinstance(child, ast.Import): + import_calls.append(child) + elif isinstance(child, ast.ImportFrom): + import_from_calls.append(child) def parse_constructor(self, node): """ @@ -1175,7 +1197,7 @@ def visit_Name(self, node): self.cur_inline_test.check_stmts = comparisons def parse_import(self, node): - # Differentiate between import, from import, and import alias + # TODO: Differentiate between import, from import, and import alias import_node = ast.Import( names=[ ast.alias(name=node) @@ -1183,6 +1205,8 @@ def parse_import(self, node): ) return import_node + def parse_import_from(self, node): + pass def build_fail(self): equal_node = ast.Compare( @@ -1234,12 +1258,12 @@ def parse_parameterized_test(self): def parse_inline_test(self, node): import_calls = [] - inline_test_calls = [] + import_from_calls = [] + inline_test_calls = [] - self.collect_inline_test_calls(node, inline_test_calls) - self.collect_import_calls(node, import_calls) + self.collect_inline_test_calls(node, inline_test_calls, import_calls, import_from_calls) + self.collect_import_calls(node, import_calls, import_from_calls) - #Why reverse? inline_test_calls.reverse() if len(inline_test_calls) <= 1: @@ -1268,6 +1292,12 @@ def parse_inline_test(self, node): else: break + for import_stmt in import_calls: + self.cur_inline_test.import_stmts.append(import_stmt) + for import_stmt in import_from_calls: + self.cur_inline_test.import_stmts.append(import_stmt) + + # "check_eq" or "check_true" or "check_false" or "check_neq" for call in inline_test_calls[inline_test_call_index:]: # "check_eq(a, 1)" @@ -1404,10 +1434,10 @@ def find(self, obj, module=None, globs=None, extraglobs=None, imports=None): globs["__name__"] = "__main__" # provide a default module name # Find intersection between loaded modules and module imports - if imports is None: - imports = set(sys.modules) & set(globs) - else: - imports = imports.copy() + # if imports is None: + # imports = set(sys.modules) & set(globs) + # else: + # imports = imports.copy() # Recursively explore `obj`, extracting InlineTests. tests = [] @@ -1431,7 +1461,7 @@ def _find(self, tests, obj, module, globs, imports, seen): # Recurse to functions & classes. if (self._is_routine(val) or inspect.isclass(val)) and self._from_module(module, val): - self._find(tests, val, module, globs, seen) + self._find(tests, val, module, globs, imports, seen) # Look for tests in a class's contained objects. if inspect.isclass(obj) and self._recurse: @@ -1445,7 +1475,7 @@ def _find(self, tests, obj, module, globs, imports, seen): module, val ): valname = "%s" % (valname) - self._find(tests, val, module, globs, seen) + self._find(tests, val, module, globs, imports, seen) ###################################################################### diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 06d25f5..be452ac 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -8,6 +8,22 @@ # pytest -p pytester class TestInlinetests: + def test_inline_detects_imports(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import numpy as np + import scipy + + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + def test_inline_parser(self, pytester: Pytester): checkfile = pytester.makepyfile( """ @@ -34,6 +50,7 @@ def m(a): items, reprec = pytester.inline_genitems(x) assert len(items) == 0 + def test_inline_malformed_given(self, pytester: Pytester): checkfile = pytester.makepyfile( """ From e1e7dbc02d6776c5a9693ce5bd97de51ee4a6604 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Sun, 2 Nov 2025 14:57:46 -0500 Subject: [PATCH 13/28] Inline Tests Account for Basic Imports (with aliases) --- src/inline/plugin.py | 27 +- tests/test_plugin.py | 1435 +++++++++++++++++++++--------------------- 2 files changed, 740 insertions(+), 722 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 4cf1c76..a84e33f 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -175,11 +175,23 @@ def __init__(self): self.devices = None self.globs = {} + def write_imports(self): + import_str = "" + for n in self.import_stmts: + import_str += ExtractInlineTest.node_to_source_code(n) + "\n" + return import_str + def to_test(self): + prefix = "\n" + + # for n in self.import_stmts: + # import_str += ExtractInlineTest.node_to_source_code(n) + "\n" + + if self.prev_stmt_type == PrevStmtType.CondExpr: if self.assume_stmts == []: - return "\n".join( - [ExtractInlineTest.node_to_source_code(n) for n in self.given_stmts] + return prefix.join( + + [ExtractInlineTest.node_to_source_code(n) for n in self.given_stmts] + [ExtractInlineTest.node_to_source_code(n) for n in self.check_stmts] ) else: @@ -188,11 +200,11 @@ def to_test(self): ) assume_statement = self.assume_stmts[0] assume_node = self.build_assume_node(assume_statement, body_nodes) - return "\n".join(ExtractInlineTest.node_to_source_code(assume_node)) + return prefix.join(ExtractInlineTest.node_to_source_code(assume_node)) else: if self.assume_stmts is None or self.assume_stmts == []: - return "\n".join( + return prefix.join( [ExtractInlineTest.node_to_source_code(n) for n in self.given_stmts] + [ExtractInlineTest.node_to_source_code(n) for n in self.previous_stmts] + [ExtractInlineTest.node_to_source_code(n) for n in self.check_stmts] @@ -203,7 +215,7 @@ def to_test(self): ) assume_statement = self.assume_stmts[0] assume_node = self.build_assume_node(assume_statement, body_nodes) - return "\n".join([ExtractInlineTest.node_to_source_code(assume_node)]) + return prefix.join([ExtractInlineTest.node_to_source_code(assume_node)]) def build_assume_node(self, assumption_node, body_nodes): return ast.If(assumption_node, body_nodes, []) @@ -1483,9 +1495,10 @@ def _find(self, tests, obj, module, globs, imports, seen): ###################################################################### class InlineTestRunner: def run(self, test: InlineTest, out: List) -> None: - test_str = test.to_test() + test_str = test.write_imports() + test_str += test.to_test() print(test_str) - tree = ast.parse(test.to_test()) + tree = ast.parse(test_str) codeobj = compile(tree, filename="", mode="exec") start_time = time.time() if test.timeout > 0: diff --git a/tests/test_plugin.py b/tests/test_plugin.py index be452ac..2b7030c 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -2,6 +2,7 @@ from _pytest.pytester import Pytester import pytest +# For testing in Spyder only if __name__ == "__main__": pytest.main(['-v', '-s']) @@ -13,727 +14,731 @@ def test_inline_detects_imports(self, pytester: Pytester): """ from inline import itest import numpy as np - import scipy def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - - def test_inline_parser(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - assert isinstance(items[0], InlinetestItem) - - def test_inline_missing_import(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - - - def test_inline_malformed_given(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_value(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a).check_eq(2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_check_eq(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a, 2, 3) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_check_true(self, pytester: Pytester): - # [Name(id='a', ctx=Load()), Constant(value=1)] - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_true(a = 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_if(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - if a > 1: - itest().given(a, 2).check_true(Group(0)) - a = a + 1 - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_check_eq_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_eq(a, [3, 4]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 2 - res = pytester.runpytest() - assert res.ret == 0 - - def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_malformed_given_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_check_true_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 2 - res = pytester.runpytest() - assert res.ret == 0 - - def test_check_false_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 2 - res = pytester.runpytest() - assert res.ret == 0 - - def test_check_non_positive_repeated_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(repeated = -1).given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - pytest.raises(MalformedException) - - def test_check_float_repeated_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - pytest.raises(MalformedException) - - def test_check_repeated_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(repeated = 2).given(a, 1).check_eq(a, 2) + b = np.arccos(a) + itest().given(a, -1).check_eq(b, np.pi) """ ) for x in (pytester.path, checkfile): items, reprec = pytester.inline_genitems(x) assert len(items) == 1 - - def test_check_group_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-group=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 1 - - def test_check_multiple_group_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 2 - - def test_check_incorrect_group_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 3) - a = a - 1 - itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-group=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 1 res = pytester.runpytest() - assert res.ret != 0 - - def test_check_disabled_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(disabled=True).given(a, 1).check_eq(a, 2) - a = a - 1 - itest().given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - - def test_check_disabled_value_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(disabled=True).given(a, 1).check_eq(a, 3) - a = a - 1 - itest().given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_multiple_given(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a, c): - b = a + c - itest().given(a, 2).given(c, a + 1).check_true(b == 5) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_multiple_malformed_given(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a, c): - b = a + c - itest().given(a, 2).given(c).check_true(b == 5) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_if_elif_else_logic(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - if a < 21: - b = "Not yet human" - itest().given(a, 0).check_true(b == "Not yet human") - elif a > 22: - b = 42 - itest().given(a, 25).check_true(b == 42) - else: - b = False - itest().given(a, 21).check_true(b == False) - - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 3 - res = pytester.runpytest() - assert res.ret == 0 - - def test_list_append(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(i): - j = h + i - itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_list_addition(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = [] - b = [x + 2 for x in a] - itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_regex(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - import re - from inline import itest - def m(x): - match = re.search("[0123]", x) - itest().given(x, "hel1o").check_eq(match.start(), 3) + assert res.ret != 1 + + def test_inline_detects_from_imports(self, pytester: Pytester): + " + + # def test_inline_parser(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # assert isinstance(items[0], InlinetestItem) + + # def test_inline_missing_import(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + + + # def test_inline_malformed_given(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_value(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a).check_eq(2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_check_eq(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a, 2, 3) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_check_true(self, pytester: Pytester): + # # [Name(id='a', ctx=Load()), Constant(value=1)] + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_true(a = 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_if(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # if a > 1: + # itest().given(a, 2).check_true(Group(0)) + # a = a + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_check_eq_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_eq(a, [3, 4]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 2 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_malformed_given_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_check_true_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 2 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_check_false_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 2 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_check_non_positive_repeated_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(repeated = -1).given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # pytest.raises(MalformedException) + + # def test_check_float_repeated_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # pytest.raises(MalformedException) + + # def test_check_repeated_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(repeated = 2).given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + + # def test_check_group_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-group=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 1 + + # def test_check_multiple_group_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 2 + + # def test_check_incorrect_group_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-group=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret != 0 + + # def test_check_disabled_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(disabled=True).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest().given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + + # def test_check_disabled_value_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(disabled=True).given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest().given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_multiple_given(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a, c): + # b = a + c + # itest().given(a, 2).given(c, a + 1).check_true(b == 5) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_multiple_malformed_given(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a, c): + # b = a + c + # itest().given(a, 2).given(c).check_true(b == 5) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_if_elif_else_logic(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # if a < 21: + # b = "Not yet human" + # itest().given(a, 0).check_true(b == "Not yet human") + # elif a > 22: + # b = 42 + # itest().given(a, 25).check_true(b == 42) + # else: + # b = False + # itest().given(a, 21).check_true(b == False) + + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 3 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_list_append(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(i): + # j = h + i + # itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_list_addition(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = [] + # b = [x + 2 for x in a] + # itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_regex(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # import re + # from inline import itest + # def m(x): + # match = re.search("[0123]", x) + # itest().given(x, "hel1o").check_eq(match.start(), 3) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_multivariate(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a, b, c, d): - e = a + b * c - d - itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_time(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import time - def m(a): - a = a + 1 - itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) - - def loop(b): - while True: - b = b + 1 - """ - ) - for x in (pytester.path, checkfile): - res = pytester.runpytest() - res.ret == 1 - pytest.raises(TimeoutException) - - def test_check_order_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 2 - assert items[0].dtest.test_name == "2" - assert items[1].dtest.test_name == "1" - - def test_check_order_and_nonorder_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a + 2 - itest("2").given(a, 1).check_eq(a, 3) - a = a - 1 - itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 3 - - assert items[0].dtest.test_name == "3" - assert items[1].dtest.test_name == "1" - assert items[2].dtest.test_name == "2" - - def test_check_same_tag_order_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a + 2 - itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) - a = a - 1 - itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - a = a + 2 - itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 4 - - assert items[0].dtest.test_name == "3" - assert items[1].dtest.test_name == "1" - assert items[2].dtest.test_name == "2" - assert items[3].dtest.test_name == "4" - - def test_check_group_and_order_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run( - "--inlinetest-order=minus", - "--inlinetest-order=add", - "--inlinetest-group=add", - ) - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 1 - - def test_assert_neq(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a - 1 - itest().given(a, 1).check_neq(a, 1) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_none(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = None - itest().given(a, 1).check_none(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_not_none(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = 3 - itest().given(a, 1).check_not_none(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_same(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - b = a - itest().given(a, "Hi").check_same(a,b) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_not_same(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - b = a + "a" - itest().given(a, "Hi").check_not_same(a,b) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_fail_statement(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - b = a + "a" - itest().given(a, "Hi").fail() - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - - def test_assume_correct(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = 3 - itest().assume(True).given(a, 1).check_not_none(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assume_correct_with_timeout(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import time - import sys - print(sys.version) - - def m(a): - a = -3 - itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) - - def loop(b): - while True: - b = b + 1 - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - # Should timeout instead of throwing AssertError - assert res.ret == 1 - - def test_assume_incorrect_with_timeout(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import time - import sys - print(sys.version) - - def m(a): - a = -3 - itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) - - def loop(b): - while True: - b = b + 1 - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - # Should not return any assert - assert res.ret == 0 - - def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def test_mtd(a, c): - b = a + c - itest().given(a, 2).given(c, a + 1).check_true(b == 5) - assert False - """ - ) - for x in (pytester.path, checkfile): - pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def test_mtd(): - a = 1 - c = 1 - b = a + c - itest().given(a, 2).given(c, a + 1).check_true(b == 5) - assert False - """ - ) - for x in (pytester.path, checkfile): - pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_multivariate(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a, b, c, d): + # e = a + b * c - d + # itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_time(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import time + # def m(a): + # a = a + 1 + # itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) + + # def loop(b): + # while True: + # b = b + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # res = pytester.runpytest() + # res.ret == 1 + # pytest.raises(TimeoutException) + + # def test_check_order_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 2 + # assert items[0].dtest.test_name == "2" + # assert items[1].dtest.test_name == "1" + + # def test_check_order_and_nonorder_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a + 2 + # itest("2").given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 3 + + # assert items[0].dtest.test_name == "3" + # assert items[1].dtest.test_name == "1" + # assert items[2].dtest.test_name == "2" + + # def test_check_same_tag_order_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a + 2 + # itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + # a = a + 2 + # itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 4 + + # assert items[0].dtest.test_name == "3" + # assert items[1].dtest.test_name == "1" + # assert items[2].dtest.test_name == "2" + # assert items[3].dtest.test_name == "4" + + # def test_check_group_and_order_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run( + # "--inlinetest-order=minus", + # "--inlinetest-order=add", + # "--inlinetest-group=add", + # ) + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 1 + + # def test_assert_neq(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a - 1 + # itest().given(a, 1).check_neq(a, 1) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_none(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = None + # itest().given(a, 1).check_none(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_not_none(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = 3 + # itest().given(a, 1).check_not_none(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_same(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # b = a + # itest().given(a, "Hi").check_same(a,b) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_not_same(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # b = a + "a" + # itest().given(a, "Hi").check_not_same(a,b) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_fail_statement(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # b = a + "a" + # itest().given(a, "Hi").fail() + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + + # def test_assume_correct(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = 3 + # itest().assume(True).given(a, 1).check_not_none(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assume_correct_with_timeout(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import time + # import sys + # print(sys.version) + + # def m(a): + # a = -3 + # itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) + + # def loop(b): + # while True: + # b = b + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # # Should timeout instead of throwing AssertError + # assert res.ret == 1 + + # def test_assume_incorrect_with_timeout(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import time + # import sys + # print(sys.version) + + # def m(a): + # a = -3 + # itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) + + # def loop(b): + # while True: + # b = b + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # # Should not return any assert + # assert res.ret == 0 + + # def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def test_mtd(a, c): + # b = a + c + # itest().given(a, 2).given(c, a + 1).check_true(b == 5) + # assert False + # """ + # ) + # for x in (pytester.path, checkfile): + # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def test_mtd(): + # a = 1 + # c = 1 + # b = a + c + # itest().given(a, 2).given(c, a + 1).check_true(b == 5) + # assert False + # """ + # ) + # for x in (pytester.path, checkfile): + # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 1 From 605f991c6f2de338f323d5fac8db2e64ef6e36f5 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Sun, 2 Nov 2025 15:15:05 -0500 Subject: [PATCH 14/28] Successfully Implemented Library Import Functionality - Inline test strings now add import statements at top of test - Created 2 tests for import functionality (one with standard import, one with from import, both with aliases) - Added meeting notes from 10-31 --- meeting-notes/10-31 Meeting Notes.txt | 27 + tests/test_plugin.py | 1443 +++++++++++++------------ 2 files changed, 756 insertions(+), 714 deletions(-) create mode 100644 meeting-notes/10-31 Meeting Notes.txt diff --git a/meeting-notes/10-31 Meeting Notes.txt b/meeting-notes/10-31 Meeting Notes.txt new file mode 100644 index 0000000..97140a4 --- /dev/null +++ b/meeting-notes/10-31 Meeting Notes.txt @@ -0,0 +1,27 @@ +- Are tests run directly after parsing, or are they amended to a file first? +-- Ad hoc file + +- Need more information on the control flow of the module? +-- Will ask +-- Entry point is VisitExpr +--- Traverses ast tree, if current node is expr, it triggers the function +--- If the expression is an inline test class, it starts with itest() and will trigger past inline test + +- Where does module determine that a line in the module is an inline test? +-- Will collect all calls and will keep collect all relevant st nodes in file; at some point, will try to pass nodes into inline tests + +- What is the root node of the ast in collect_inline_test_calls()? +-- Inline test expression +-- Inline tests are generated separately with each individual itest() call + +# Ideas +- Find should return list of tests AND imports +- Add import_list to ExtractInlineTest class; would search for imports in the module at the same time as inline tests +- Furthermore, collect import calls while collecting ineline_test_calls in "parse_inline_test" +- From research, found that you could get list of imports loaded by module by getting intersection between system modules and module imports, though that would be redundant since we can just access the source code directly +- Can define strings for "import", "from", and "as"; would like to know if ast's group code by line + + +Import statements may be in classes or functions + +Add references to imported libraries to ensure they are imported properly \ No newline at end of file diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 2b7030c..fca16a8 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -27,718 +27,733 @@ def m(a): assert res.ret != 1 def test_inline_detects_from_imports(self, pytester: Pytester): - " - - # def test_inline_parser(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # assert isinstance(items[0], InlinetestItem) - - # def test_inline_missing_import(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - - - # def test_inline_malformed_given(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_value(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a).check_eq(2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_check_eq(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a, 2, 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_check_true(self, pytester: Pytester): - # # [Name(id='a', ctx=Load()), Constant(value=1)] - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_true(a = 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_if(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # if a > 1: - # itest().given(a, 2).check_true(Group(0)) - # a = a + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_check_eq_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_eq(a, [3, 4]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 2 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_malformed_given_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_check_true_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 2 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_check_false_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 2 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_check_non_positive_repeated_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(repeated = -1).given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # pytest.raises(MalformedException) - - # def test_check_float_repeated_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # pytest.raises(MalformedException) - - # def test_check_repeated_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(repeated = 2).given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - - # def test_check_group_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-group=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 1 - - # def test_check_multiple_group_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 2 - - # def test_check_incorrect_group_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-group=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret != 0 - - # def test_check_disabled_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(disabled=True).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest().given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - - # def test_check_disabled_value_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(disabled=True).given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest().given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_multiple_given(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a, c): - # b = a + c - # itest().given(a, 2).given(c, a + 1).check_true(b == 5) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_multiple_malformed_given(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a, c): - # b = a + c - # itest().given(a, 2).given(c).check_true(b == 5) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_if_elif_else_logic(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # if a < 21: - # b = "Not yet human" - # itest().given(a, 0).check_true(b == "Not yet human") - # elif a > 22: - # b = 42 - # itest().given(a, 25).check_true(b == 42) - # else: - # b = False - # itest().given(a, 21).check_true(b == False) - - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 3 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_list_append(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(i): - # j = h + i - # itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_list_addition(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = [] - # b = [x + 2 for x in a] - # itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_regex(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # import re - # from inline import itest - # def m(x): - # match = re.search("[0123]", x) - # itest().given(x, "hel1o").check_eq(match.start(), 3) + checkfile = pytester.makepyfile( + """ + from inline import itest + import numpy as np + from scipy import stats as st + + def m(n, p): + b = st.binom(n, p) + itest().given(n, 100).given(p, 0.5).check_eq(b.mean(), n * p) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_inline_parser(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + assert isinstance(items[0], InlinetestItem) + + def test_inline_missing_import(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + + + def test_inline_malformed_given(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_value(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a).check_eq(2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_check_eq(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a, 2, 3) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_check_true(self, pytester: Pytester): + # [Name(id='a', ctx=Load()), Constant(value=1)] + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_true(a = 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_if(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + if a > 1: + itest().given(a, 2).check_true(Group(0)) + a = a + 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_check_eq_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_eq(a, [3, 4]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 2 + res = pytester.runpytest() + assert res.ret == 0 + + def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_malformed_given_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_check_true_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 2 + res = pytester.runpytest() + assert res.ret == 0 + + def test_check_false_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 2 + res = pytester.runpytest() + assert res.ret == 0 + + def test_check_non_positive_repeated_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(repeated = -1).given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + pytest.raises(MalformedException) + + def test_check_float_repeated_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + pytest.raises(MalformedException) + + def test_check_repeated_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(repeated = 2).given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + + def test_check_group_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-group=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 1 + + def test_check_multiple_group_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 2 + + def test_check_incorrect_group_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 3) + a = a - 1 + itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-group=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret != 0 + + def test_check_disabled_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(disabled=True).given(a, 1).check_eq(a, 2) + a = a - 1 + itest().given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + + def test_check_disabled_value_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(disabled=True).given(a, 1).check_eq(a, 3) + a = a - 1 + itest().given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_multiple_given(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a, c): + b = a + c + itest().given(a, 2).given(c, a + 1).check_true(b == 5) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_multiple_malformed_given(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a, c): + b = a + c + itest().given(a, 2).given(c).check_true(b == 5) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_if_elif_else_logic(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + if a < 21: + b = "Not yet human" + itest().given(a, 0).check_true(b == "Not yet human") + elif a > 22: + b = 42 + itest().given(a, 25).check_true(b == 42) + else: + b = False + itest().given(a, 21).check_true(b == False) + + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 3 + res = pytester.runpytest() + assert res.ret == 0 + + def test_list_append(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(i): + j = h + i + itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_list_addition(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = [] + b = [x + 2 for x in a] + itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_regex(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + import re + from inline import itest + def m(x): + match = re.search("[0123]", x) + itest().given(x, "hel1o").check_eq(match.start(), 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_multivariate(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a, b, c, d): - # e = a + b * c - d - # itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_time(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import time - # def m(a): - # a = a + 1 - # itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) - - # def loop(b): - # while True: - # b = b + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # res = pytester.runpytest() - # res.ret == 1 - # pytest.raises(TimeoutException) - - # def test_check_order_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 2 - # assert items[0].dtest.test_name == "2" - # assert items[1].dtest.test_name == "1" - - # def test_check_order_and_nonorder_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a + 2 - # itest("2").given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 3 - - # assert items[0].dtest.test_name == "3" - # assert items[1].dtest.test_name == "1" - # assert items[2].dtest.test_name == "2" - - # def test_check_same_tag_order_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a + 2 - # itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - # a = a + 2 - # itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 4 - - # assert items[0].dtest.test_name == "3" - # assert items[1].dtest.test_name == "1" - # assert items[2].dtest.test_name == "2" - # assert items[3].dtest.test_name == "4" - - # def test_check_group_and_order_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run( - # "--inlinetest-order=minus", - # "--inlinetest-order=add", - # "--inlinetest-group=add", - # ) - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 1 - - # def test_assert_neq(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a - 1 - # itest().given(a, 1).check_neq(a, 1) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_none(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = None - # itest().given(a, 1).check_none(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_not_none(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = 3 - # itest().given(a, 1).check_not_none(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_same(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # b = a - # itest().given(a, "Hi").check_same(a,b) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_not_same(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # b = a + "a" - # itest().given(a, "Hi").check_not_same(a,b) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_fail_statement(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # b = a + "a" - # itest().given(a, "Hi").fail() - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - - # def test_assume_correct(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = 3 - # itest().assume(True).given(a, 1).check_not_none(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assume_correct_with_timeout(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import time - # import sys - # print(sys.version) - - # def m(a): - # a = -3 - # itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) - - # def loop(b): - # while True: - # b = b + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # # Should timeout instead of throwing AssertError - # assert res.ret == 1 - - # def test_assume_incorrect_with_timeout(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import time - # import sys - # print(sys.version) - - # def m(a): - # a = -3 - # itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) - - # def loop(b): - # while True: - # b = b + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # # Should not return any assert - # assert res.ret == 0 - - # def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def test_mtd(a, c): - # b = a + c - # itest().given(a, 2).given(c, a + 1).check_true(b == 5) - # assert False - # """ - # ) - # for x in (pytester.path, checkfile): - # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def test_mtd(): - # a = 1 - # c = 1 - # b = a + c - # itest().given(a, 2).given(c, a + 1).check_true(b == 5) - # assert False - # """ - # ) - # for x in (pytester.path, checkfile): - # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_multivariate(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a, b, c, d): + e = a + b * c - d + itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_time(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import time + def m(a): + a = a + 1 + itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) + + def loop(b): + while True: + b = b + 1 + """ + ) + for x in (pytester.path, checkfile): + res = pytester.runpytest() + res.ret == 1 + pytest.raises(TimeoutException) + + def test_check_order_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 2 + assert items[0].dtest.test_name == "2" + assert items[1].dtest.test_name == "1" + + def test_check_order_and_nonorder_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a + 2 + itest("2").given(a, 1).check_eq(a, 3) + a = a - 1 + itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 3 + + assert items[0].dtest.test_name == "3" + assert items[1].dtest.test_name == "1" + assert items[2].dtest.test_name == "2" + + def test_check_same_tag_order_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a + 2 + itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) + a = a - 1 + itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + a = a + 2 + itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 4 + + assert items[0].dtest.test_name == "3" + assert items[1].dtest.test_name == "1" + assert items[2].dtest.test_name == "2" + assert items[3].dtest.test_name == "4" + + def test_check_group_and_order_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run( + "--inlinetest-order=minus", + "--inlinetest-order=add", + "--inlinetest-group=add", + ) + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 1 + + def test_assert_neq(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a - 1 + itest().given(a, 1).check_neq(a, 1) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_none(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = None + itest().given(a, 1).check_none(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_not_none(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = 3 + itest().given(a, 1).check_not_none(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_same(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + b = a + itest().given(a, "Hi").check_same(a,b) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_not_same(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + b = a + "a" + itest().given(a, "Hi").check_not_same(a,b) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_fail_statement(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + b = a + "a" + itest().given(a, "Hi").fail() + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + + def test_assume_correct(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = 3 + itest().assume(True).given(a, 1).check_not_none(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assume_correct_with_timeout(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import time + import sys + print(sys.version) + + def m(a): + a = -3 + itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) + + def loop(b): + while True: + b = b + 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + # Should timeout instead of throwing AssertError + assert res.ret == 1 + + def test_assume_incorrect_with_timeout(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import time + import sys + print(sys.version) + + def m(a): + a = -3 + itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) + + def loop(b): + while True: + b = b + 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + # Should not return any assert + assert res.ret == 0 + + def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def test_mtd(a, c): + b = a + c + itest().given(a, 2).given(c, a + 1).check_true(b == 5) + assert False + """ + ) + for x in (pytester.path, checkfile): + pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def test_mtd(): + a = 1 + c = 1 + b = a + c + itest().given(a, 2).given(c, a + 1).check_true(b == 5) + assert False + """ + ) + for x in (pytester.path, checkfile): + pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 1 From f0b41739b20ca82a4ee14525d7e8b6c3fd99c564 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 7 Nov 2025 14:28:11 -0500 Subject: [PATCH 15/28] Began Refactoring First Section of Constructor --- src/inline/plugin.py | 129 ++++++++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 33 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index a84e33f..6e89c77 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -8,6 +8,7 @@ from pathlib import Path from types import ModuleType from typing import Any, Dict, Iterable, List, Optional, Tuple, Union +from enum import Enum import pytest from _pytest.pathlib import fnmatch_ex @@ -417,40 +418,62 @@ def parse_constructor(self, node): # 5) timeout (positive float) # 6) devices (str array) + class ConstrArgs(Enum): + TEST_NAME = 0 + PARAMETERIZED = 1 + REPEATED = 2 + TAG_STR = 3 + DISABLED = 4 + TIMEOUT = 5 + DEVICES = 6 + + property_names = { + ConstrArgs.TEST_NAME : "test_name", + ConstrArgs.PARAMETERIZED : "parameterized", + ConstrArgs.REPEATED : "repeated", + ConstrArgs.TAG_STR : "tag", + ConstrArgs.DISABLED : "disabled", + ConstrArgs.TIMEOUT : "timeout", + ConstrArgs.DEVICES : "devices" + } + + pre_38_val_names = { + ConstrArgs.TEST_NAME : "s", + ConstrArgs.PARAMETERIZED : "value", + ConstrArgs.REPEATED : "n", + ConstrArgs.TAG_STR : "s", + ConstrArgs.DISABLED : "value", + ConstrArgs.TIMEOUT : "n", + ConstrArgs.DEVICES : "" + } + + pre_38_expec_ast_arg_type = { + ConstrArgs.TEST_NAME : ast.Str, + ConstrArgs.PARAMETERIZED : ast.NameConstant, + ConstrArgs.REPEATED : ast.Num, + ConstrArgs.TAG_STR : ast.List, + ConstrArgs.DISABLED : ast.NameConstant, + ConstrArgs.TIMEOUT : ast.Num, + } + + expected_ast_val_args = { + ConstrArgs.TEST_NAME : [str], + ConstrArgs.PARAMETERIZED : [bool], + ConstrArgs.REPEATED : [int], + ConstrArgs.TAG_STR : [ast.List], + ConstrArgs.DISABLED : [bool], + ConstrArgs.TIMEOUT : [float, int], + ConstrArgs.DEVICES : [str] + } + NUM_OF_ARGUMENTS = 7 if len(node.args) + len(node.keywords) <= NUM_OF_ARGUMENTS: # positional arguments if sys.version_info >= (3, 8, 0): + # Arguments organized by expected ast type, value type, and index in that order for index, arg in enumerate(node.args): - # check if "test_name" is a string - if index == 0 and isinstance(arg, ast.Constant) and isinstance(arg.value, str): - # get the test name if exists - self.cur_inline_test.test_name = arg.value - # check if "parameterized" is a boolean - elif index == 1 and isinstance(arg, ast.Constant) and isinstance(arg.value, bool): - self.cur_inline_test.parameterized = arg.value - # check if "repeated" is a positive integer - elif index == 2 and isinstance(arg, ast.Constant) and isinstance(arg.value, int): - if arg.value <= 0: - raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - self.cur_inline_test.repeated = arg.value - elif index == 3 and isinstance(arg.value, ast.List): - tags = [] - for elt in arg.value.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException(f"tag can only be List of string") - tags.append(elt.value) - self.cur_inline_test.tag = tags - elif index == 4 and isinstance(arg, ast.Constant) and isinstance(arg.value, bool): - self.cur_inline_test.disabled = arg.value - elif ( - index == 5 - and isinstance(arg, ast.Constant) - and (isinstance(arg.value, float) or isinstance(arg.value, int)) - ): - self.cur_inline_test.timeout = arg.value - - elif index == 6 and isinstance(arg, ast.List): + # Devices are not referenced in versions before 3.8; all other arguments can be from any version + if index == ConstrArgs.DEVICES and isinstance(arg, ast.List): devices = [] for elt in arg.elts: if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): @@ -459,11 +482,51 @@ def parse_constructor(self, node): raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") devices.append(elt.value) self.cur_inline_test.devices = devices - + # Assumes version is past 3.8, no explicit references to ast.Constant before 3.8 else: - raise MalformedException( - f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" - ) + corr_arg_type = False + corr_val_type = False + value_prop_name = "" + + if sys.version_info >= (3, 8, 0) and isinstance(arg, ast.Constant): + corr_arg_type = True + value_prop_name = "value" + elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[index]): + corr_arg_type = True + value_prop_name = pre_38_val_names[index] + + # Verifies value types + for arg_type in expected_ast_val_args[index]: + if isinstance(arg.value, arg_type): + corr_val_type = True + break + + + if corr_val_type and corr_arg_type: + # Accounts for additional checks for REPEATED and TAG_STR arguments + match index: + case ConstrArgs.REPEATED: + if arg.value <= 0: + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") + self.cur_inline_test.repeated = getattr(arg, value_prop_name) + case ConstrArgs.TAG_STR: + tags = [] + for elt in arg.value.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException(f"tag can only be List of string") + tags.append(getattr(elt, value_prop_name)) + self.cur_inline_test.tag = tags + # For non-special cases, set the attribute defined by the dictionary + case _: + setattr(self.cur_inline_test, + property_names[index], + getattr(arg, value_prop_name)) + else: + raise MalformedException( + f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" + ) + #raise MalformedException("Argument " + str(index) + " incorrectly formatted. Argument should be a " + ConstrArgs.expected_ast_val_args[index].type()) + # keyword arguments for keyword in node.keywords: # check if "test_name" is a string From 1677514c93c3052e94df458664b386179341b045 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 7 Nov 2025 14:53:12 -0500 Subject: [PATCH 16/28] Create 11-7 Meeting Notes.txt --- meeting-notes/11-7 Meeting Notes.txt | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 meeting-notes/11-7 Meeting Notes.txt diff --git a/meeting-notes/11-7 Meeting Notes.txt b/meeting-notes/11-7 Meeting Notes.txt new file mode 100644 index 0000000..9231a28 --- /dev/null +++ b/meeting-notes/11-7 Meeting Notes.txt @@ -0,0 +1,29 @@ +Cases where libraries are not installed: +- Throw bad error message +- Should be the developer's duty to set up their environment properly with the libraries they want to use +- Add to pytest configuration file + +Devices would eventually be moved out of constructor +For differential tests, we would introduce diff_input function + +diff_given(): +- Parameters: +- Specific +-- Passes in differential test inputs +-- Want to eliminate the devices constructor; devices would only support specific inputs +-- Supports any differential input variable to be given +--- Example: testing on devices, written as diff_given(devices, ["cuda", "cpu"]) +--- Reference how given is written right now +-- Would be from the inline test call itself +-- Devices is only accepted first argument in diff_given +--- Should be equivalent to what it was +--- Can try to extend for any first argument +-- Can be used together with given() +--- Some variables shouldn't be given multiple values; not changing the values of multiple inputs +--- Use given() to specific input value for some variables +--- Use diff_given() for inputs that need to be varied +-- Refer to project proposal for format +-- Order can be varied + +Fork original repo, then copy changes over + From 5e835eef2235b7811d2f4012b19ac7fb3c6f8f51 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 7 Nov 2025 15:46:28 -0500 Subject: [PATCH 17/28] KeyError Fix --- src/inline/plugin.py | 123 ++++++++++++++++++++++++++++++++++++++++--- tests/test_plugin.py | 4 +- 2 files changed, 117 insertions(+), 10 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 6e89c77..cd84914 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -8,7 +8,6 @@ from pathlib import Path from types import ModuleType from typing import Any, Dict, Iterable, List, Optional, Tuple, Union -from enum import Enum import pytest from _pytest.pathlib import fnmatch_ex @@ -418,7 +417,7 @@ def parse_constructor(self, node): # 5) timeout (positive float) # 6) devices (str array) - class ConstrArgs(Enum): + class ConstrArgs(enum.Enum): TEST_NAME = 0 PARAMETERIZED = 1 REPEATED = 2 @@ -487,16 +486,17 @@ class ConstrArgs(Enum): corr_arg_type = False corr_val_type = False value_prop_name = "" + arg_idx = ConstrArgs(index) if sys.version_info >= (3, 8, 0) and isinstance(arg, ast.Constant): corr_arg_type = True value_prop_name = "value" - elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[index]): + elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[arg_idx]): corr_arg_type = True - value_prop_name = pre_38_val_names[index] + value_prop_name = pre_38_val_names[arg_idx] # Verifies value types - for arg_type in expected_ast_val_args[index]: + for arg_type in expected_ast_val_args[arg_idx]: if isinstance(arg.value, arg_type): corr_val_type = True break @@ -504,7 +504,7 @@ class ConstrArgs(Enum): if corr_val_type and corr_arg_type: # Accounts for additional checks for REPEATED and TAG_STR arguments - match index: + match arg_idx: case ConstrArgs.REPEATED: if arg.value <= 0: raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") @@ -519,14 +519,13 @@ class ConstrArgs(Enum): # For non-special cases, set the attribute defined by the dictionary case _: setattr(self.cur_inline_test, - property_names[index], + property_names[arg_idx], getattr(arg, value_prop_name)) else: raise MalformedException( f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" ) #raise MalformedException("Argument " + str(index) + " incorrectly formatted. Argument should be a " + ConstrArgs.expected_ast_val_args[index].type()) - # keyword arguments for keyword in node.keywords: # check if "test_name" is a string @@ -712,6 +711,114 @@ class ConstrArgs(Enum): # set the line number self.cur_inline_test.lineno = node.lineno + def parse_constructor_args(self, args): + class ConstrArgs(enum.Enum): + TEST_NAME = 0 + PARAMETERIZED = 1 + REPEATED = 2 + TAG_STR = 3 + DISABLED = 4 + TIMEOUT = 5 + DEVICES = 6 + + property_names = { + ConstrArgs.TEST_NAME : "test_name", + ConstrArgs.PARAMETERIZED : "parameterized", + ConstrArgs.REPEATED : "repeated", + ConstrArgs.TAG_STR : "tag", + ConstrArgs.DISABLED : "disabled", + ConstrArgs.TIMEOUT : "timeout", + ConstrArgs.DEVICES : "devices" + } + + pre_38_val_names = { + ConstrArgs.TEST_NAME : "s", + ConstrArgs.PARAMETERIZED : "value", + ConstrArgs.REPEATED : "n", + ConstrArgs.TAG_STR : "s", + ConstrArgs.DISABLED : "value", + ConstrArgs.TIMEOUT : "n", + ConstrArgs.DEVICES : "" + } + + pre_38_expec_ast_arg_type = { + ConstrArgs.TEST_NAME : ast.Str, + ConstrArgs.PARAMETERIZED : ast.NameConstant, + ConstrArgs.REPEATED : ast.Num, + ConstrArgs.TAG_STR : ast.List, + ConstrArgs.DISABLED : ast.NameConstant, + ConstrArgs.TIMEOUT : ast.Num, + } + + expected_ast_val_args = { + ConstrArgs.TEST_NAME : [str], + ConstrArgs.PARAMETERIZED : [bool], + ConstrArgs.REPEATED : [int], + ConstrArgs.TAG_STR : [ast.List], + ConstrArgs.DISABLED : [bool], + ConstrArgs.TIMEOUT : [float, int], + ConstrArgs.DEVICES : [str] + } + + # positional arguments + if sys.version_info >= (3, 8, 0): + # Arguments organized by expected ast type, value type, and index in that order + for index, arg in args: + # Devices are not referenced in versions before 3.8; all other arguments can be from any version + if index == ConstrArgs.DEVICES and isinstance(arg, ast.List): + devices = [] + for elt in arg.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException("devices can only be List of string") + if elt.value not in {"cpu", "cuda", "mps"}: + raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") + devices.append(elt.value) + self.cur_inline_test.devices = devices + # Assumes version is past 3.8, no explicit references to ast.Constant before 3.8 + else: + corr_arg_type = False + corr_val_type = False + value_prop_name = "" + + if sys.version_info >= (3, 8, 0) and isinstance(arg, ast.Constant): + corr_arg_type = True + value_prop_name = "value" + elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[index]): + corr_arg_type = True + value_prop_name = pre_38_val_names[index] + + # Verifies value types + for arg_type in expected_ast_val_args[index]: + if isinstance(arg.value, arg_type): + corr_val_type = True + break + + + if corr_val_type and corr_arg_type: + # Accounts for additional checks for REPEATED and TAG_STR arguments + match index: + case ConstrArgs.REPEATED: + if arg.value <= 0: + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") + self.cur_inline_test.repeated = getattr(arg, value_prop_name) + case ConstrArgs.TAG_STR: + tags = [] + for elt in arg.value.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException(f"tag can only be List of string") + tags.append(getattr(elt, value_prop_name)) + self.cur_inline_test.tag = tags + # For non-special cases, set the attribute defined by the dictionary + case _: + setattr(self.cur_inline_test, + property_names[index], + getattr(arg, value_prop_name)) + else: + raise MalformedException( + f"inline test: {self.class_name_str}() accepts {len(ConstrArgs)} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" + ) + #raise MalformedException("Argument " + str(index) + " incorrectly formatted. Argument should be a " + ConstrArgs.expected_ast_val_args[index].type()) + def parameterized_inline_tests_init(self, node: ast.List): if not self.cur_inline_test.parameterized_inline_tests: self.cur_inline_test.parameterized_inline_tests = [InlineTest() for _ in range(len(node.elts))] diff --git a/tests/test_plugin.py b/tests/test_plugin.py index fca16a8..b94c957 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3,8 +3,8 @@ import pytest # For testing in Spyder only -if __name__ == "__main__": - pytest.main(['-v', '-s']) +# if __name__ == "__main__": +# pytest.main(['-v', '-s']) # pytest -p pytester From 93c5e52f4f7c401e36547cbb059e720d7d22f7a7 Mon Sep 17 00:00:00 2001 From: hanse7962 Date: Thu, 13 Nov 2025 12:15:42 -0500 Subject: [PATCH 18/28] feat:Began Diff Given Parsing --- src/inline/inline.py | 10 ++++++++++ src/inline/plugin.py | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/inline/inline.py b/src/inline/inline.py index 847c253..17acf7b 100644 --- a/src/inline/inline.py +++ b/src/inline/inline.py @@ -35,6 +35,16 @@ def given(self, variable, value): """ return self + def diff_given(self, variable, value): + """ + Set value to a variable for differential testing. + + :param variable: a variable name + :param value: a value that will be assigned to the variable + :returns: Inline object + """ + return self + def check_eq(self, actual_value, expected_value): """ Assert whether two values equal diff --git a/src/inline/plugin.py b/src/inline/plugin.py index a84e33f..d065ddc 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -300,6 +300,7 @@ class ExtractInlineTest(ast.NodeTransformer): check_not_same = "check_not_same" fail_str = "fail" given_str = "given" + diff_given_str = "diff_given" group_str = "Group" arg_test_name_str = "test_name" arg_parameterized_str = "parameterized" @@ -668,6 +669,23 @@ def parse_given(self, node): else: raise MalformedException("inline test: invalid given(), expected 2 args") + def parse_diff_given(self, node): + PROPERTY = 0 + VALUES = 1 + + if len(node.args) == 2: + if self.cur_inline_test.parameterized: + pass + else: + devices = [] + for elt in node.args[VALUES].elts: + if elt.value not in {"cpu", "cuda", "mps"}: + raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") + devices.append(elt.value) + self.cur_inline_test.devices = devices + else: + raise MalformedException("inline test: invalid diff_given(), expected 2 args") + def parse_assume(self, node): if len(node.args) == 1: if self.cur_inline_test.parameterized: @@ -1296,14 +1314,24 @@ def parse_inline_test(self, node): self.parse_assume(call) inline_test_call_index += 1 - # "given(a, 1)" for call in inline_test_calls[inline_test_call_index:]: - if isinstance(call.func, ast.Attribute) and call.func.attr == self.given_str: - self.parse_given(call) - inline_test_call_index += 1 + if isinstance(call.func, ast.Attribute): + match call.func.attr: + # "given(a, 1)" + case self.given_str: + self.parse_given(call) + inline_test_call_index += 1 + # "diff_given(devices, ["cpu", "cuda"])" + case self.diff_given_str: + self.parse_diff_given(call) + inline_test_call_index += 1 else: break + + else: + break + for import_stmt in import_calls: self.cur_inline_test.import_stmts.append(import_stmt) for import_stmt in import_from_calls: From ac65f1a4804fc3a89d4ce94c3eb718ff578622d1 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 12:13:28 -0500 Subject: [PATCH 19/28] Confirmed Devices Assignment --- src/inline/plugin.py | 8 +- tests/test_plugin.py | 1489 +++++++++++++++++++++--------------------- 2 files changed, 755 insertions(+), 742 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index e3823d0..f4ed9f7 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -845,14 +845,14 @@ def parse_diff_given(self, node): if len(node.args) == 2: if self.cur_inline_test.parameterized: - pass + raise MalformedException("inline test: diff_given() does not currently support parameterized inline tests.") else: devices = [] for elt in node.args[VALUES].elts: if elt.value not in {"cpu", "cuda", "mps"}: raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") devices.append(elt.value) - self.cur_inline_test.devices = devices + setattr(self.cur_inline_test, node.args[PROPERTY].id, devices) else: raise MalformedException("inline test: invalid diff_given(), expected 2 args") @@ -1498,10 +1498,6 @@ def parse_inline_test(self, node): else: break - - else: - break - for import_stmt in import_calls: self.cur_inline_test.import_stmts.append(import_stmt) for import_stmt in import_from_calls: diff --git a/tests/test_plugin.py b/tests/test_plugin.py index b94c957..114be07 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3,21 +3,20 @@ import pytest # For testing in Spyder only -# if __name__ == "__main__": -# pytest.main(['-v', '-s']) +if __name__ == "__main__": + pytest.main(['-v', '-s']) # pytest -p pytester class TestInlinetests: - def test_inline_detects_imports(self, pytester: Pytester): + def test_inline_diff_given(self, pytester: Pytester): checkfile = pytester.makepyfile( """ from inline import itest - import numpy as np def m(a): - b = np.arccos(a) - itest().given(a, -1).check_eq(b, np.pi) + a = a + 1 + itest().diff_given(devices, ["cpu", "cuda"]).given(a, 1).check_eq(a, 2) """ ) for x in (pytester.path, checkfile): @@ -25,735 +24,753 @@ def m(a): assert len(items) == 1 res = pytester.runpytest() assert res.ret != 1 - - def test_inline_detects_from_imports(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import numpy as np - from scipy import stats as st - def m(n, p): - b = st.binom(n, p) - itest().given(n, 100).given(p, 0.5).check_eq(b.mean(), n * p) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_inline_parser(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - assert isinstance(items[0], InlinetestItem) - - def test_inline_missing_import(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - - - def test_inline_malformed_given(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_value(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a).check_eq(2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_check_eq(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_eq(a, 2, 3) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_inline_malformed_check_true(self, pytester: Pytester): - # [Name(id='a', ctx=Load()), Constant(value=1)] - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest().given(a, 1).check_true(a = 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_if(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - if a > 1: - itest().given(a, 2).check_true(Group(0)) - a = a + 1 - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_check_eq_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_eq(a, [3, 4]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 2 - res = pytester.runpytest() - assert res.ret == 0 - - def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_malformed_given_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_check_true_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 2 - res = pytester.runpytest() - assert res.ret == 0 - - def test_check_false_parameterized_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 2 - res = pytester.runpytest() - assert res.ret == 0 - - def test_check_non_positive_repeated_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(repeated = -1).given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - pytest.raises(MalformedException) - - def test_check_float_repeated_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - pytest.raises(MalformedException) - - def test_check_repeated_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(repeated = 2).given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - - def test_check_group_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-group=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 1 - - def test_check_multiple_group_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 2 - - def test_check_incorrect_group_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 3) - a = a - 1 - itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-group=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret != 0 - - def test_check_disabled_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(disabled=True).given(a, 1).check_eq(a, 2) - a = a - 1 - itest().given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - - def test_check_disabled_value_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(disabled=True).given(a, 1).check_eq(a, 3) - a = a - 1 - itest().given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_multiple_given(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a, c): - b = a + c - itest().given(a, 2).given(c, a + 1).check_true(b == 5) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_multiple_malformed_given(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a, c): - b = a + c - itest().given(a, 2).given(c).check_true(b == 5) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - pytest.raises(MalformedException) - - def test_if_elif_else_logic(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - if a < 21: - b = "Not yet human" - itest().given(a, 0).check_true(b == "Not yet human") - elif a > 22: - b = 42 - itest().given(a, 25).check_true(b == 42) - else: - b = False - itest().given(a, 21).check_true(b == False) - - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 3 - res = pytester.runpytest() - assert res.ret == 0 - - def test_list_append(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(i): - j = h + i - itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_list_addition(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = [] - b = [x + 2 for x in a] - itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_regex(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - import re - from inline import itest - def m(x): - match = re.search("[0123]", x) - itest().given(x, "hel1o").check_eq(match.start(), 3) + + # def test_inline_detects_imports(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import numpy as np + + # def m(a): + # b = np.arccos(a) + # itest().given(a, -1).check_eq(b, np.pi) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret != 1 + + # def test_inline_detects_from_imports(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import numpy as np + # from scipy import stats as st + + # def m(n, p): + # b = st.binom(n, p) + # itest().given(n, 100).given(p, 0.5).check_eq(b.mean(), n * p) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_inline_parser(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # assert isinstance(items[0], InlinetestItem) + + # def test_inline_missing_import(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + + + # def test_inline_malformed_given(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_value(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a).check_eq(2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_check_eq(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_eq(a, 2, 3) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_inline_malformed_check_true(self, pytester: Pytester): + # # [Name(id='a', ctx=Load()), Constant(value=1)] + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest().given(a, 1).check_true(a = 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_if(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # if a > 1: + # itest().given(a, 2).check_true(Group(0)) + # a = a + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_check_eq_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_eq(a, [3, 4]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 2 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_malformed_given_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_check_true_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 2 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_check_false_parameterized_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 2 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_check_non_positive_repeated_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(repeated = -1).given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # pytest.raises(MalformedException) + + # def test_check_float_repeated_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # pytest.raises(MalformedException) + + # def test_check_repeated_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(repeated = 2).given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + + # def test_check_group_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-group=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 1 + + # def test_check_multiple_group_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 2 + + # def test_check_incorrect_group_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-group=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret != 0 + + # def test_check_disabled_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(disabled=True).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest().given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + + # def test_check_disabled_value_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(disabled=True).given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest().given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_multiple_given(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a, c): + # b = a + c + # itest().given(a, 2).given(c, a + 1).check_true(b == 5) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_multiple_malformed_given(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a, c): + # b = a + c + # itest().given(a, 2).given(c).check_true(b == 5) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + # pytest.raises(MalformedException) + + # def test_if_elif_else_logic(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # if a < 21: + # b = "Not yet human" + # itest().given(a, 0).check_true(b == "Not yet human") + # elif a > 22: + # b = 42 + # itest().given(a, 25).check_true(b == 42) + # else: + # b = False + # itest().given(a, 21).check_true(b == False) + + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 3 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_list_append(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(i): + # j = h + i + # itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_list_addition(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = [] + # b = [x + 2 for x in a] + # itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_regex(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # import re + # from inline import itest + # def m(x): + # match = re.search("[0123]", x) + # itest().given(x, "hel1o").check_eq(match.start(), 3) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_multivariate(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a, b, c, d): - e = a + b * c - d - itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_time(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import time - def m(a): - a = a + 1 - itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) - - def loop(b): - while True: - b = b + 1 - """ - ) - for x in (pytester.path, checkfile): - res = pytester.runpytest() - res.ret == 1 - pytest.raises(TimeoutException) - - def test_check_order_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 2 - assert items[0].dtest.test_name == "2" - assert items[1].dtest.test_name == "1" - - def test_check_order_and_nonorder_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a + 2 - itest("2").given(a, 1).check_eq(a, 3) - a = a - 1 - itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 3 - - assert items[0].dtest.test_name == "3" - assert items[1].dtest.test_name == "1" - assert items[2].dtest.test_name == "2" - - def test_check_same_tag_order_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a + 2 - itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) - a = a - 1 - itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - a = a + 2 - itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 4 - - assert items[0].dtest.test_name == "3" - assert items[1].dtest.test_name == "1" - assert items[2].dtest.test_name == "2" - assert items[3].dtest.test_name == "4" - - def test_check_group_and_order_tests(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a + 1 - itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - a = a - 1 - itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) - """ - ) - for x in (pytester.path, checkfile): - reprec = pytester.inline_run( - "--inlinetest-order=minus", - "--inlinetest-order=add", - "--inlinetest-group=add", - ) - items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - assert len(items) == 1 - - def test_assert_neq(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = a - 1 - itest().given(a, 1).check_neq(a, 1) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_none(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = None - itest().given(a, 1).check_none(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_not_none(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = 3 - itest().given(a, 1).check_not_none(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_same(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - b = a - itest().given(a, "Hi").check_same(a,b) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assert_not_same(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - b = a + "a" - itest().given(a, "Hi").check_not_same(a,b) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_fail_statement(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - b = a + "a" - itest().given(a, "Hi").fail() - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 0 - - def test_assume_correct(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def m(a): - a = 3 - itest().assume(True).given(a, 1).check_not_none(a) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_assume_correct_with_timeout(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import time - import sys - print(sys.version) - - def m(a): - a = -3 - itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) - - def loop(b): - while True: - b = b + 1 - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - # Should timeout instead of throwing AssertError - assert res.ret == 1 - - def test_assume_incorrect_with_timeout(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import time - import sys - print(sys.version) - - def m(a): - a = -3 - itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) - - def loop(b): - while True: - b = b + 1 - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - # Should not return any assert - assert res.ret == 0 - - def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def test_mtd(a, c): - b = a + c - itest().given(a, 2).given(c, a + 1).check_true(b == 5) - assert False - """ - ) - for x in (pytester.path, checkfile): - pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 - - def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - def test_mtd(): - a = 1 - c = 1 - b = a + c - itest().given(a, 2).given(c, a + 1).check_true(b == 5) - assert False - """ - ) - for x in (pytester.path, checkfile): - pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_multivariate(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a, b, c, d): + # e = a + b * c - d + # itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_time(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import time + # def m(a): + # a = a + 1 + # itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) + + # def loop(b): + # while True: + # b = b + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # res = pytester.runpytest() + # res.ret == 1 + # pytest.raises(TimeoutException) + + # def test_check_order_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 2 + # assert items[0].dtest.test_name == "2" + # assert items[1].dtest.test_name == "1" + + # def test_check_order_and_nonorder_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a + 2 + # itest("2").given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 3 + + # assert items[0].dtest.test_name == "3" + # assert items[1].dtest.test_name == "1" + # assert items[2].dtest.test_name == "2" + + # def test_check_same_tag_order_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a + 2 + # itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) + # a = a - 1 + # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + # a = a + 2 + # itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 4 + + # assert items[0].dtest.test_name == "3" + # assert items[1].dtest.test_name == "1" + # assert items[2].dtest.test_name == "2" + # assert items[3].dtest.test_name == "4" + + # def test_check_group_and_order_tests(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a + 1 + # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + # a = a - 1 + # itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) + # """ + # ) + # for x in (pytester.path, checkfile): + # reprec = pytester.inline_run( + # "--inlinetest-order=minus", + # "--inlinetest-order=add", + # "--inlinetest-group=add", + # ) + # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + # assert len(items) == 1 + + # def test_assert_neq(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = a - 1 + # itest().given(a, 1).check_neq(a, 1) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_none(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = None + # itest().given(a, 1).check_none(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_not_none(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = 3 + # itest().given(a, 1).check_not_none(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_same(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # b = a + # itest().given(a, "Hi").check_same(a,b) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assert_not_same(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # b = a + "a" + # itest().given(a, "Hi").check_not_same(a,b) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_fail_statement(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # b = a + "a" + # itest().given(a, "Hi").fail() + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 0 + + # def test_assume_correct(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def m(a): + # a = 3 + # itest().assume(True).given(a, 1).check_not_none(a) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_assume_correct_with_timeout(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import time + # import sys + # print(sys.version) + + # def m(a): + # a = -3 + # itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) + + # def loop(b): + # while True: + # b = b + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # # Should timeout instead of throwing AssertError + # assert res.ret == 1 + + # def test_assume_incorrect_with_timeout(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import time + # import sys + # print(sys.version) + + # def m(a): + # a = -3 + # itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) + + # def loop(b): + # while True: + # b = b + 1 + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # # Should not return any assert + # assert res.ret == 0 + + # def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def test_mtd(a, c): + # b = a + c + # itest().given(a, 2).given(c, a + 1).check_true(b == 5) + # assert False + # """ + # ) + # for x in (pytester.path, checkfile): + # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 + + # def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # def test_mtd(): + # a = 1 + # c = 1 + # b = a + c + # itest().given(a, 2).given(c, a + 1).check_true(b == 5) + # assert False + # """ + # ) + # for x in (pytester.path, checkfile): + # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 1 From 94cda4a0cd3545aebd536d8f8c96f828e8aa0ff6 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 12:55:10 -0500 Subject: [PATCH 20/28] More Constructor Tweaking --- src/inline/plugin.py | 261 ++++++++++++++++++++----------------------- tests/test_plugin.py | 58 +++++----- 2 files changed, 151 insertions(+), 168 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index f4ed9f7..ca905cb 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -466,140 +466,108 @@ class ConstrArgs(enum.Enum): ConstrArgs.DEVICES : [str] } + keyword_idxs = { + self.arg_test_name_str : ConstrArgs.TEST_NAME, + self.arg_parameterized_str : ConstrArgs.PARAMETERIZED, + self.arg_repeated_str : ConstrArgs.REPEATED, + self.arg_tag_str : ConstrArgs.TAG_STR, + self.arg_disabled_str : ConstrArgs.DISABLED, + self.arg_timeout_str : ConstrArgs.TIMEOUT, + self.arg_devices_str : ConstrArgs.DEVICES + } + NUM_OF_ARGUMENTS = 7 if len(node.args) + len(node.keywords) <= NUM_OF_ARGUMENTS: # positional arguments if sys.version_info >= (3, 8, 0): - # Arguments organized by expected ast type, value type, and index in that order - for index, arg in enumerate(node.args): - # Devices are not referenced in versions before 3.8; all other arguments can be from any version - if index == ConstrArgs.DEVICES and isinstance(arg, ast.List): - devices = [] - for elt in arg.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException("devices can only be List of string") - if elt.value not in {"cpu", "cuda", "mps"}: - raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") - devices.append(elt.value) - self.cur_inline_test.devices = devices - # Assumes version is past 3.8, no explicit references to ast.Constant before 3.8 - else: - corr_arg_type = False - corr_val_type = False - value_prop_name = "" - arg_idx = ConstrArgs(index) - - if sys.version_info >= (3, 8, 0) and isinstance(arg, ast.Constant): - corr_arg_type = True - value_prop_name = "value" - elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[arg_idx]): - corr_arg_type = True - value_prop_name = pre_38_val_names[arg_idx] - - # Verifies value types - for arg_type in expected_ast_val_args[arg_idx]: - if isinstance(arg.value, arg_type): - corr_val_type = True - break - - - if corr_val_type and corr_arg_type: - # Accounts for additional checks for REPEATED and TAG_STR arguments - match arg_idx: - case ConstrArgs.REPEATED: - if arg.value <= 0: - raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - self.cur_inline_test.repeated = getattr(arg, value_prop_name) - case ConstrArgs.TAG_STR: - tags = [] - for elt in arg.value.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException(f"tag can only be List of string") - tags.append(getattr(elt, value_prop_name)) - self.cur_inline_test.tag = tags - # For non-special cases, set the attribute defined by the dictionary - case _: - setattr(self.cur_inline_test, - property_names[arg_idx], - getattr(arg, value_prop_name)) - else: - raise MalformedException( - f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" - ) - #raise MalformedException("Argument " + str(index) + " incorrectly formatted. Argument should be a " + ConstrArgs.expected_ast_val_args[index].type()) - # keyword arguments + # positional arguments + self.parse_constructor_args(node.args) + + #keyword arguments + keyword_args = [] + + #create list with 7 null values (for each position) + for i in range(0, NUM_OF_ARGUMENTS): + keyword_args.append(None) + for keyword in node.keywords: - # check if "test_name" is a string - if ( - keyword.arg == self.arg_test_name_str - and isinstance(keyword.value, ast.Constant) - and isinstance(keyword.value.value, str) - ): - self.cur_inline_test.test_name = keyword.value.value - # check if "parameterized" is a boolean - elif ( - keyword.arg == self.arg_parameterized_str - and isinstance(keyword.value, ast.Constant) - and isinstance(keyword.value.value, bool) - ): - self.cur_inline_test.parameterized = keyword.value.value - # check if "repeated" is a positive integer - elif ( - keyword.arg == self.arg_repeated_str - and isinstance(keyword.value, ast.Constant) - and isinstance(keyword.value.value, int) - ): - if keyword.value.value <= 0: - raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - self.cur_inline_test.repeated = keyword.value.value - # check if "tag" is a list of string - elif keyword.arg == self.arg_tag_str and isinstance(keyword.value, ast.List): - tags = [] - for elt in keyword.value.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException(f"tag can only be List of string") - tags.append(elt.value) - self.cur_inline_test.tag = tags - # Add devices handling for keyword args - elif keyword.arg == self.arg_devices_str and isinstance(keyword.value, ast.List): - devices = [] - for elt in keyword.value.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException("devices can only be List of string") - if elt.value not in {"cpu", "cuda", "mps"}: - raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") - devices.append(elt.value) - self.cur_inline_test.devices = devices - # check if "disabled" is a boolean - elif ( - keyword.arg == self.arg_disabled_str - and isinstance(keyword.value, ast.Constant) - and isinstance(keyword.value.value, bool) - ): - self.cur_inline_test.disabled = keyword.value.value - # check if "timeout" is a positive float - elif ( - keyword.arg == self.arg_timeout_str - and isinstance(keyword.value, ast.Constant) - and (isinstance(keyword.value.value, float) or isinstance(keyword.value.value, int)) - ): - if keyword.value.value <= 0.0: - raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") - self.cur_inline_test.timeout = keyword.value.value - # Add devices handling for Python 3.7 - elif index == 6 and isinstance(arg, ast.List): - devices = [] - for elt in arg.elts: - if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): # Note: ast.Str for Python 3.7 - raise MalformedException("devices can only be List of string") - if elt.s not in {"cpu", "cuda", "mps"}: - raise MalformedException(f"Invalid device: {elt.s}. Must be one of ['cpu', 'cuda', 'mps']") - devices.append(elt.s) - self.cur_inline_test.devices = devices - else: - raise MalformedException( - f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" - ) + keyword_args[keyword_idxs[keyword.arg].value] = keyword + self.parse_constructor_args(keyword_args) + + + # # keyword arguments + # for keyword in node.keywords: + # # check if "test_name" is a string + # if ( + # keyword.arg == self.arg_test_name_str + # and isinstance(keyword.value, ast.Constant) + # and isinstance(keyword.value.value, str) + # ): + # self.cur_inline_test.test_name = keyword.value.value + # # check if "parameterized" is a boolean + # elif ( + # keyword.arg == self.arg_parameterized_str + # and isinstance(keyword.value, ast.Constant) + # and isinstance(keyword.value.value, bool) + # ): + # self.cur_inline_test.parameterized = keyword.value.value + # # check if "repeated" is a positive integer + # elif ( + # keyword.arg == self.arg_repeated_str + # and isinstance(keyword.value, ast.Constant) + # and isinstance(keyword.value.value, int) + # ): + # if keyword.value.value <= 0: + # raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") + # self.cur_inline_test.repeated = keyword.value.value + # # check if "tag" is a list of string + # elif keyword.arg == self.arg_tag_str and isinstance(keyword.value, ast.List): + # tags = [] + # for elt in keyword.value.elts: + # if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + # raise MalformedException(f"tag can only be List of string") + # tags.append(elt.value) + # self.cur_inline_test.tag = tags + # # Add devices handling for keyword args + # elif keyword.arg == self.arg_devices_str and isinstance(keyword.value, ast.List): + # devices = [] + # for elt in keyword.value.elts: + # if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + # raise MalformedException("devices can only be List of string") + # if elt.value not in {"cpu", "cuda", "mps"}: + # raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") + # devices.append(elt.value) + # self.cur_inline_test.devices = devices + # # check if "disabled" is a boolean + # elif ( + # keyword.arg == self.arg_disabled_str + # and isinstance(keyword.value, ast.Constant) + # and isinstance(keyword.value.value, bool) + # ): + # self.cur_inline_test.disabled = keyword.value.value + # # check if "timeout" is a positive float + # elif ( + # keyword.arg == self.arg_timeout_str + # and isinstance(keyword.value, ast.Constant) + # and (isinstance(keyword.value.value, float) or isinstance(keyword.value.value, int)) + # ): + # if keyword.value.value <= 0.0: + # raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") + # self.cur_inline_test.timeout = keyword.value.value + # # Add devices handling for Python 3.7 + # elif index == 6 and isinstance(arg, ast.List): + # devices = [] + # for elt in arg.elts: + # if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): # Note: ast.Str for Python 3.7 + # raise MalformedException("devices can only be List of string") + # if elt.s not in {"cpu", "cuda", "mps"}: + # raise MalformedException(f"Invalid device: {elt.s}. Must be one of ['cpu', 'cuda', 'mps']") + # devices.append(elt.s) + # self.cur_inline_test.devices = devices + # else: + # raise MalformedException( + # f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" + # ) else: for index, arg in enumerate(node.args): # check if "test_name" is a string @@ -721,7 +689,7 @@ class ConstrArgs(enum.Enum): DISABLED = 4 TIMEOUT = 5 DEVICES = 6 - + property_names = { ConstrArgs.TEST_NAME : "test_name", ConstrArgs.PARAMETERIZED : "parameterized", @@ -761,10 +729,24 @@ class ConstrArgs(enum.Enum): ConstrArgs.DEVICES : [str] } - # positional arguments + keyword_idxs = { + self.arg_test_name_str : ConstrArgs.TEST_NAME, + self.arg_parameterized_str : ConstrArgs.PARAMETERIZED, + self.arg_repeated_str : ConstrArgs.REPEATED, + self.arg_tag_str : ConstrArgs.TAG_STR, + self.arg_disabled_str : ConstrArgs.DISABLED, + self.arg_timeout_str : ConstrArgs.TIMEOUT, + self.arg_devices_str : ConstrArgs.DEVICES + } + NUM_OF_ARGUMENTS = 7 + if sys.version_info >= (3, 8, 0): # Arguments organized by expected ast type, value type, and index in that order - for index, arg in args: + for index, arg in enumerate(args): + # Skips over null arguments; needed for keywords + if arg == None: + continue + # Devices are not referenced in versions before 3.8; all other arguments can be from any version if index == ConstrArgs.DEVICES and isinstance(arg, ast.List): devices = [] @@ -780,16 +762,17 @@ class ConstrArgs(enum.Enum): corr_arg_type = False corr_val_type = False value_prop_name = "" + arg_idx = ConstrArgs(index) - if sys.version_info >= (3, 8, 0) and isinstance(arg, ast.Constant): + if sys.version_info >= (3, 8, 0) and isinstance(arg, ast.Constant) or isinstance(arg, ast.keyword): corr_arg_type = True value_prop_name = "value" - elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[index]): + elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[arg_idx]): corr_arg_type = True - value_prop_name = pre_38_val_names[index] + value_prop_name = pre_38_val_names[arg_idx] # Verifies value types - for arg_type in expected_ast_val_args[index]: + for arg_type in expected_ast_val_args[arg_idx]: if isinstance(arg.value, arg_type): corr_val_type = True break @@ -797,7 +780,7 @@ class ConstrArgs(enum.Enum): if corr_val_type and corr_arg_type: # Accounts for additional checks for REPEATED and TAG_STR arguments - match index: + match arg_idx: case ConstrArgs.REPEATED: if arg.value <= 0: raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") @@ -812,11 +795,11 @@ class ConstrArgs(enum.Enum): # For non-special cases, set the attribute defined by the dictionary case _: setattr(self.cur_inline_test, - property_names[index], + property_names[arg_idx], getattr(arg, value_prop_name)) else: raise MalformedException( - f"inline test: {self.class_name_str}() accepts {len(ConstrArgs)} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" + f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" ) #raise MalformedException("Argument " + str(index) + " incorrectly formatted. Argument should be a " + ConstrArgs.expected_ast_val_args[index].type()) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 114be07..6044f40 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -9,21 +9,21 @@ # pytest -p pytester class TestInlinetests: - def test_inline_diff_given(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest + # def test_inline_diff_given(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest - def m(a): - a = a + 1 - itest().diff_given(devices, ["cpu", "cuda"]).given(a, 1).check_eq(a, 2) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret != 1 + # def m(a): + # a = a + 1 + # itest().diff_given(devices, ["cpu", "cuda"]).given(a, 1).check_eq(a, 2) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret != 1 # def test_inline_detects_imports(self, pytester: Pytester): @@ -159,21 +159,21 @@ def m(a): # assert len(items) == 0 # pytest.raises(MalformedException) - # def test_if(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # if a > 1: - # itest().given(a, 2).check_true(Group(0)) - # a = a + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 + def test_if(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + if a > 1: + itest().given(a, 2).check_true(Group(0)) + a = a + 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 # def test_check_eq_parameterized_tests(self, pytester: Pytester): # checkfile = pytester.makepyfile( From 1e6160d75a56fdca7de9e0e35383e1342f2ec8e4 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 13:25:28 -0500 Subject: [PATCH 21/28] Constructor Parameter Fixes --- src/inline/plugin.py | 99 +-- tests/test_plugin.py | 1469 +++++++++++++++++++++--------------------- 2 files changed, 745 insertions(+), 823 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index ca905cb..028cbb5 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -191,7 +191,7 @@ def to_test(self): if self.prev_stmt_type == PrevStmtType.CondExpr: if self.assume_stmts == []: return prefix.join( - + [ExtractInlineTest.node_to_source_code(n) for n in self.given_stmts] + [ExtractInlineTest.node_to_source_code(n) for n in self.given_stmts] + [ExtractInlineTest.node_to_source_code(n) for n in self.check_stmts] ) else: @@ -491,83 +491,9 @@ class ConstrArgs(enum.Enum): keyword_args.append(None) for keyword in node.keywords: - keyword_args[keyword_idxs[keyword.arg].value] = keyword + keyword_args[keyword_idxs[keyword.arg].value] = keyword.value self.parse_constructor_args(keyword_args) - - # # keyword arguments - # for keyword in node.keywords: - # # check if "test_name" is a string - # if ( - # keyword.arg == self.arg_test_name_str - # and isinstance(keyword.value, ast.Constant) - # and isinstance(keyword.value.value, str) - # ): - # self.cur_inline_test.test_name = keyword.value.value - # # check if "parameterized" is a boolean - # elif ( - # keyword.arg == self.arg_parameterized_str - # and isinstance(keyword.value, ast.Constant) - # and isinstance(keyword.value.value, bool) - # ): - # self.cur_inline_test.parameterized = keyword.value.value - # # check if "repeated" is a positive integer - # elif ( - # keyword.arg == self.arg_repeated_str - # and isinstance(keyword.value, ast.Constant) - # and isinstance(keyword.value.value, int) - # ): - # if keyword.value.value <= 0: - # raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - # self.cur_inline_test.repeated = keyword.value.value - # # check if "tag" is a list of string - # elif keyword.arg == self.arg_tag_str and isinstance(keyword.value, ast.List): - # tags = [] - # for elt in keyword.value.elts: - # if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - # raise MalformedException(f"tag can only be List of string") - # tags.append(elt.value) - # self.cur_inline_test.tag = tags - # # Add devices handling for keyword args - # elif keyword.arg == self.arg_devices_str and isinstance(keyword.value, ast.List): - # devices = [] - # for elt in keyword.value.elts: - # if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - # raise MalformedException("devices can only be List of string") - # if elt.value not in {"cpu", "cuda", "mps"}: - # raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") - # devices.append(elt.value) - # self.cur_inline_test.devices = devices - # # check if "disabled" is a boolean - # elif ( - # keyword.arg == self.arg_disabled_str - # and isinstance(keyword.value, ast.Constant) - # and isinstance(keyword.value.value, bool) - # ): - # self.cur_inline_test.disabled = keyword.value.value - # # check if "timeout" is a positive float - # elif ( - # keyword.arg == self.arg_timeout_str - # and isinstance(keyword.value, ast.Constant) - # and (isinstance(keyword.value.value, float) or isinstance(keyword.value.value, int)) - # ): - # if keyword.value.value <= 0.0: - # raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") - # self.cur_inline_test.timeout = keyword.value.value - # # Add devices handling for Python 3.7 - # elif index == 6 and isinstance(arg, ast.List): - # devices = [] - # for elt in arg.elts: - # if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): # Note: ast.Str for Python 3.7 - # raise MalformedException("devices can only be List of string") - # if elt.s not in {"cpu", "cuda", "mps"}: - # raise MalformedException(f"Invalid device: {elt.s}. Must be one of ['cpu', 'cuda', 'mps']") - # devices.append(elt.s) - # self.cur_inline_test.devices = devices - # else: - # raise MalformedException( - # f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" - # ) else: for index, arg in enumerate(node.args): # check if "test_name" is a string @@ -719,11 +645,20 @@ class ConstrArgs(enum.Enum): ConstrArgs.TIMEOUT : ast.Num, } + expected_ast_arg_type = { + ConstrArgs.TEST_NAME : ast.Constant, + ConstrArgs.PARAMETERIZED : ast.Constant, + ConstrArgs.REPEATED : ast.Constant, + ConstrArgs.TAG_STR : ast.List, + ConstrArgs.DISABLED : ast.Constant, + ConstrArgs.TIMEOUT : ast.Constant + } + expected_ast_val_args = { ConstrArgs.TEST_NAME : [str], ConstrArgs.PARAMETERIZED : [bool], ConstrArgs.REPEATED : [int], - ConstrArgs.TAG_STR : [ast.List], + ConstrArgs.TAG_STR : [None], ConstrArgs.DISABLED : [bool], ConstrArgs.TIMEOUT : [float, int], ConstrArgs.DEVICES : [str] @@ -764,20 +699,22 @@ class ConstrArgs(enum.Enum): value_prop_name = "" arg_idx = ConstrArgs(index) - if sys.version_info >= (3, 8, 0) and isinstance(arg, ast.Constant) or isinstance(arg, ast.keyword): + if sys.version_info >= (3, 8, 0) and isinstance(arg, expected_ast_arg_type[arg_idx]): corr_arg_type = True value_prop_name = "value" elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[arg_idx]): corr_arg_type = True value_prop_name = pre_38_val_names[arg_idx] - # Verifies value types + # Verifies value types; skipped for ast node types with no nested values for arg_type in expected_ast_val_args[arg_idx]: + if arg_type == None: + corr_val_type = True + break if isinstance(arg.value, arg_type): corr_val_type = True break - if corr_val_type and corr_arg_type: # Accounts for additional checks for REPEATED and TAG_STR arguments match arg_idx: @@ -787,7 +724,7 @@ class ConstrArgs(enum.Enum): self.cur_inline_test.repeated = getattr(arg, value_prop_name) case ConstrArgs.TAG_STR: tags = [] - for elt in arg.value.elts: + for elt in arg.elts: if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): raise MalformedException(f"tag can only be List of string") tags.append(getattr(elt, value_prop_name)) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 6044f40..fb28c59 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -9,155 +9,155 @@ # pytest -p pytester class TestInlinetests: - # def test_inline_diff_given(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest + def test_inline_diff_given(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest - # def m(a): - # a = a + 1 - # itest().diff_given(devices, ["cpu", "cuda"]).given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret != 1 + def m(a): + a = a + 1 + itest().diff_given(devices, ["cpu", "cuda"]).given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret != 1 - # def test_inline_detects_imports(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import numpy as np + def test_inline_detects_imports(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import numpy as np - # def m(a): - # b = np.arccos(a) - # itest().given(a, -1).check_eq(b, np.pi) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret != 1 - - # def test_inline_detects_from_imports(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import numpy as np - # from scipy import stats as st + def m(a): + b = np.arccos(a) + itest().given(a, -1).check_eq(b, np.pi) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret != 1 + + def test_inline_detects_from_imports(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import numpy as np + from scipy import stats as st - # def m(n, p): - # b = st.binom(n, p) - # itest().given(n, 100).given(p, 0.5).check_eq(b.mean(), n * p) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_inline_parser(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # assert isinstance(items[0], InlinetestItem) - - # def test_inline_missing_import(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - - - # def test_inline_malformed_given(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_value(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a).check_eq(2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_check_eq(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_eq(a, 2, 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_inline_malformed_check_true(self, pytester: Pytester): - # # [Name(id='a', ctx=Load()), Constant(value=1)] - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest().given(a, 1).check_true(a = 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) + def m(n, p): + b = st.binom(n, p) + itest().given(n, 100).given(p, 0.5).check_eq(b.mean(), n * p) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_inline_parser(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + assert isinstance(items[0], InlinetestItem) + + def test_inline_missing_import(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + + + def test_inline_malformed_given(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_value(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a).check_eq(2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_check_eq(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_check_eq_more_argument(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_eq(a, 2, 3) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_inline_malformed_check_true(self, pytester: Pytester): + # [Name(id='a', ctx=Load()), Constant(value=1)] + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest().given(a, 1).check_true(a = 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) def test_if(self, pytester: Pytester): checkfile = pytester.makepyfile( @@ -175,602 +175,587 @@ def m(a): res = pytester.runpytest() assert res.ret == 0 - # def test_check_eq_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_eq(a, [3, 4]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 2 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_malformed_given_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_check_true_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 2 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_check_false_parameterized_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 2 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_check_non_positive_repeated_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(repeated = -1).given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # pytest.raises(MalformedException) - - # def test_check_float_repeated_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # pytest.raises(MalformedException) - - # def test_check_repeated_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(repeated = 2).given(a, 1).check_eq(a, 2) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - - # def test_check_group_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-group=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 1 - - # def test_check_multiple_group_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 2 - - # def test_check_incorrect_group_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-group=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret != 0 - - # def test_check_disabled_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(disabled=True).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest().given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - - # def test_check_disabled_value_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(disabled=True).given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest().given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_multiple_given(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a, c): - # b = a + c - # itest().given(a, 2).given(c, a + 1).check_true(b == 5) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_multiple_malformed_given(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a, c): - # b = a + c - # itest().given(a, 2).given(c).check_true(b == 5) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - # pytest.raises(MalformedException) - - # def test_if_elif_else_logic(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # if a < 21: - # b = "Not yet human" - # itest().given(a, 0).check_true(b == "Not yet human") - # elif a > 22: - # b = 42 - # itest().given(a, 25).check_true(b == 42) - # else: - # b = False - # itest().given(a, 21).check_true(b == False) - - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 3 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_list_append(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(i): - # j = h + i - # itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_list_addition(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = [] - # b = [x + 2 for x in a] - # itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_regex(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # import re - # from inline import itest - # def m(x): - # match = re.search("[0123]", x) - # itest().given(x, "hel1o").check_eq(match.start(), 3) + def test_malformed_check_eq_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_eq(a, 3) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_malformed_given_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2]).check_eq(a, [3, 4]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_check_true_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_true([a == 3, a == 4]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 2 + res = pytester.runpytest() + assert res.ret == 0 + + def test_check_false_parameterized_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 2 + res = pytester.runpytest() + assert res.ret == 0 + + def test_check_non_positive_repeated_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(repeated = -1).given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + pytest.raises(MalformedException) + + def test_check_float_repeated_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(parameterized=True).given(a, [2, 3]).check_false([a == 1, a == 2]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + pytest.raises(MalformedException) + + def test_check_repeated_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(repeated = 2).given(a, 1).check_eq(a, 2) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + + def test_check_group_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-group=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 1 + + def test_check_multiple_group_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 2 + + def test_check_incorrect_group_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 3) + a = a - 1 + itest(tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-group=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret != 0 + + def test_check_disabled_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(disabled=True).given(a, 1).check_eq(a, 2) + a = a - 1 + itest().given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + + def test_check_disabled_value_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(disabled=True).given(a, 1).check_eq(a, 3) + a = a - 1 + itest().given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_multiple_given(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a, c): + b = a + c + itest().given(a, 2).given(c, a + 1).check_true(b == 5) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_multiple_malformed_given(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a, c): + b = a + c + itest().given(a, 2).given(c).check_true(b == 5) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + pytest.raises(MalformedException) + + def test_if_elif_else_logic(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + if a < 21: + b = "Not yet human" + itest().given(a, 0).check_true(b == "Not yet human") + elif a > 22: + b = 42 + itest().given(a, 25).check_true(b == 42) + else: + b = False + itest().given(a, 21).check_true(b == False) + + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 3 + res = pytester.runpytest() + assert res.ret == 0 + + def test_list_append(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(i): + j = h + i + itest().given(i, ["pineapple", "pear"]).given(h, ["apple", "banana", "orange"]).check_eq(j, ["apple", "banana", "orange", "pineapple", "pear"]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_list_addition(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = [] + b = [x + 2 for x in a] + itest().given(a, [1,2,3,2,1]).check_true(b == [3,4,5,4,3]) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_regex(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + import re + from inline import itest + def m(x): + match = re.search("[0123]", x) + itest().given(x, "hel1o").check_eq(match.start(), 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_multivariate(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a, b, c, d): - # e = a + b * c - d - # itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_time(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import time - # def m(a): - # a = a + 1 - # itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) - - # def loop(b): - # while True: - # b = b + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # res = pytester.runpytest() - # res.ret == 1 - # pytest.raises(TimeoutException) - - # def test_check_order_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 2 - # assert items[0].dtest.test_name == "2" - # assert items[1].dtest.test_name == "1" - - # def test_check_order_and_nonorder_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a + 2 - # itest("2").given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 3 - - # assert items[0].dtest.test_name == "3" - # assert items[1].dtest.test_name == "1" - # assert items[2].dtest.test_name == "2" - - # def test_check_same_tag_order_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a + 2 - # itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) - # a = a - 1 - # itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) - # a = a + 2 - # itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 4 - - # assert items[0].dtest.test_name == "3" - # assert items[1].dtest.test_name == "1" - # assert items[2].dtest.test_name == "2" - # assert items[3].dtest.test_name == "4" - - # def test_check_group_and_order_tests(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a + 1 - # itest(tag = ["add"]).given(a, 1).check_eq(a, 2) - # a = a - 1 - # itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) - # """ - # ) - # for x in (pytester.path, checkfile): - # reprec = pytester.inline_run( - # "--inlinetest-order=minus", - # "--inlinetest-order=add", - # "--inlinetest-group=add", - # ) - # items = [x.item for x in reprec.getcalls("pytest_itemcollected")] - # assert len(items) == 1 - - # def test_assert_neq(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = a - 1 - # itest().given(a, 1).check_neq(a, 1) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_none(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = None - # itest().given(a, 1).check_none(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_not_none(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = 3 - # itest().given(a, 1).check_not_none(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_same(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # b = a - # itest().given(a, "Hi").check_same(a,b) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assert_not_same(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # b = a + "a" - # itest().given(a, "Hi").check_not_same(a,b) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_fail_statement(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # b = a + "a" - # itest().given(a, "Hi").fail() - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 0 - - # def test_assume_correct(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def m(a): - # a = 3 - # itest().assume(True).given(a, 1).check_not_none(a) - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_assume_correct_with_timeout(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import time - # import sys - # print(sys.version) - - # def m(a): - # a = -3 - # itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) - - # def loop(b): - # while True: - # b = b + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # # Should timeout instead of throwing AssertError - # assert res.ret == 1 - - # def test_assume_incorrect_with_timeout(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # import time - # import sys - # print(sys.version) - - # def m(a): - # a = -3 - # itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) - - # def loop(b): - # while True: - # b = b + 1 - # """ - # ) - # for x in (pytester.path, checkfile): - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # # Should not return any assert - # assert res.ret == 0 - - # def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def test_mtd(a, c): - # b = a + c - # itest().given(a, 2).given(c, a + 1).check_true(b == 5) - # assert False - # """ - # ) - # for x in (pytester.path, checkfile): - # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 0 - - # def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): - # checkfile = pytester.makepyfile( - # """ - # from inline import itest - # def test_mtd(): - # a = 1 - # c = 1 - # b = a + c - # itest().given(a, 2).given(c, a + 1).check_true(b == 5) - # assert False - # """ - # ) - # for x in (pytester.path, checkfile): - # pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") - # items, reprec = pytester.inline_genitems(x) - # assert len(items) == 1 - # res = pytester.runpytest() - # assert res.ret == 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_multivariate(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a, b, c, d): + e = a + b * c - d + itest().given(a, 3).given(b, 4).given(c,5).given(d,6).check_true(e == 17) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_time(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import time + def m(a): + a = a + 1 + itest(timeout=1).given(a, loop(3)).check_eq(a,4.0) + + def loop(b): + while True: + b = b + 1 + """ + ) + for x in (pytester.path, checkfile): + res = pytester.runpytest() + res.ret == 1 + pytest.raises(TimeoutException) + + def test_check_order_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest("2", tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 2 + assert items[0].dtest.test_name == "2" + assert items[1].dtest.test_name == "1" + + def test_check_order_and_nonorder_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a + 2 + itest("2").given(a, 1).check_eq(a, 3) + a = a - 1 + itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 3 + + assert items[0].dtest.test_name == "3" + assert items[1].dtest.test_name == "1" + assert items[2].dtest.test_name == "2" + + def test_check_same_tag_order_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a + 2 + itest("2", tag = ["add"]).given(a, 1).check_eq(a, 3) + a = a - 1 + itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) + a = a + 2 + itest("4", tag = ["add"]).given(a, 1).check_eq(a, 3) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 4 + + assert items[0].dtest.test_name == "3" + assert items[1].dtest.test_name == "1" + assert items[2].dtest.test_name == "2" + assert items[3].dtest.test_name == "4" + + def test_check_group_and_order_tests(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a + 1 + itest(tag = ["add"]).given(a, 1).check_eq(a, 2) + a = a - 1 + itest(tag = ["minus"]).given(a, 2).check_eq(a, 1) + """ + ) + for x in (pytester.path, checkfile): + reprec = pytester.inline_run( + "--inlinetest-order=minus", + "--inlinetest-order=add", + "--inlinetest-group=add", + ) + items = [x.item for x in reprec.getcalls("pytest_itemcollected")] + assert len(items) == 1 + + def test_assert_neq(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = a - 1 + itest().given(a, 1).check_neq(a, 1) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_none(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = None + itest().given(a, 1).check_none(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_not_none(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = 3 + itest().given(a, 1).check_not_none(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_same(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + b = a + itest().given(a, "Hi").check_same(a,b) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assert_not_same(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + b = a + "a" + itest().given(a, "Hi").check_not_same(a,b) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_fail_statement(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + b = a + "a" + itest().given(a, "Hi").fail() + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 0 + + def test_assume_correct(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def m(a): + a = 3 + itest().assume(True).given(a, 1).check_not_none(a) + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_assume_correct_with_timeout(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import time + import sys + print(sys.version) + + def m(a): + a = -3 + itest(timeout=1).assume(True).given(a, loop(3)).check_eq(a,1) + + def loop(b): + while True: + b = b + 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + # Should timeout instead of throwing AssertError + assert res.ret == 1 + + def test_assume_incorrect_with_timeout(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + import time + import sys + print(sys.version) + + def m(a): + a = -3 + itest(timeout=1).assume(False).given(a, loop(3)).check_eq(a,1) + + def loop(b): + while True: + b = b + 1 + """ + ) + for x in (pytester.path, checkfile): + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + # Should not return any assert + assert res.ret == 0 + + def test_unit_test_disable_inline_test_enable(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def test_mtd(a, c): + b = a + c + itest().given(a, 2).given(c, a + 1).check_true(b == 5) + assert False + """ + ) + for x in (pytester.path, checkfile): + pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 0 + + def test_unit_test_enable_inline_test_disable(self, pytester: Pytester): + checkfile = pytester.makepyfile( + """ + from inline import itest + def test_mtd(): + a = 1 + c = 1 + b = a + c + itest().given(a, 2).given(c, a + 1).check_true(b == 5) + assert False + """ + ) + for x in (pytester.path, checkfile): + pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") + items, reprec = pytester.inline_genitems(x) + assert len(items) == 1 + res = pytester.runpytest() + assert res.ret == 1 From e555b6cd87a1ddcd3388f291d0ac5252ba8aaf2d Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 13:31:11 -0500 Subject: [PATCH 22/28] Code Cleanup --- src/inline/plugin.py | 323 +++++++++++-------------------------------- 1 file changed, 80 insertions(+), 243 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 028cbb5..0638c82 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -418,186 +418,33 @@ def parse_constructor(self, node): # 5) timeout (positive float) # 6) devices (str array) - class ConstrArgs(enum.Enum): - TEST_NAME = 0 - PARAMETERIZED = 1 - REPEATED = 2 - TAG_STR = 3 - DISABLED = 4 - TIMEOUT = 5 - DEVICES = 6 - - property_names = { - ConstrArgs.TEST_NAME : "test_name", - ConstrArgs.PARAMETERIZED : "parameterized", - ConstrArgs.REPEATED : "repeated", - ConstrArgs.TAG_STR : "tag", - ConstrArgs.DISABLED : "disabled", - ConstrArgs.TIMEOUT : "timeout", - ConstrArgs.DEVICES : "devices" - } - - pre_38_val_names = { - ConstrArgs.TEST_NAME : "s", - ConstrArgs.PARAMETERIZED : "value", - ConstrArgs.REPEATED : "n", - ConstrArgs.TAG_STR : "s", - ConstrArgs.DISABLED : "value", - ConstrArgs.TIMEOUT : "n", - ConstrArgs.DEVICES : "" - } - - pre_38_expec_ast_arg_type = { - ConstrArgs.TEST_NAME : ast.Str, - ConstrArgs.PARAMETERIZED : ast.NameConstant, - ConstrArgs.REPEATED : ast.Num, - ConstrArgs.TAG_STR : ast.List, - ConstrArgs.DISABLED : ast.NameConstant, - ConstrArgs.TIMEOUT : ast.Num, - } - expected_ast_val_args = { - ConstrArgs.TEST_NAME : [str], - ConstrArgs.PARAMETERIZED : [bool], - ConstrArgs.REPEATED : [int], - ConstrArgs.TAG_STR : [ast.List], - ConstrArgs.DISABLED : [bool], - ConstrArgs.TIMEOUT : [float, int], - ConstrArgs.DEVICES : [str] - } keyword_idxs = { - self.arg_test_name_str : ConstrArgs.TEST_NAME, - self.arg_parameterized_str : ConstrArgs.PARAMETERIZED, - self.arg_repeated_str : ConstrArgs.REPEATED, - self.arg_tag_str : ConstrArgs.TAG_STR, - self.arg_disabled_str : ConstrArgs.DISABLED, - self.arg_timeout_str : ConstrArgs.TIMEOUT, - self.arg_devices_str : ConstrArgs.DEVICES + self.arg_test_name_str : 0, + self.arg_parameterized_str : 1, + self.arg_repeated_str : 2, + self.arg_tag_str : 3, + self.arg_disabled_str : 4, + self.arg_timeout_str : 5, + self.arg_devices_str : 6 } NUM_OF_ARGUMENTS = 7 if len(node.args) + len(node.keywords) <= NUM_OF_ARGUMENTS: # positional arguments - if sys.version_info >= (3, 8, 0): - # positional arguments - self.parse_constructor_args(node.args) - - #keyword arguments - keyword_args = [] - - #create list with 7 null values (for each position) - for i in range(0, NUM_OF_ARGUMENTS): - keyword_args.append(None) - - for keyword in node.keywords: - keyword_args[keyword_idxs[keyword.arg].value] = keyword.value - self.parse_constructor_args(keyword_args) - - else: - for index, arg in enumerate(node.args): - # check if "test_name" is a string - if index == 0 and isinstance(arg, ast.Str) and isinstance(arg.s, str): - # get the test name if exists - self.cur_inline_test.test_name = arg.s - # check if "parameterized" is a boolean - elif index == 1 and isinstance(arg, ast.NameConstant) and isinstance(arg.value, bool): - self.cur_inline_test.parameterized = arg.value - # check if "repeated" is a positive integer - elif index == 2 and isinstance(arg, ast.Num) and isinstance(arg.n, int): - if arg.n <= 0.0: - raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - self.cur_inline_test.repeated = arg.n - # check if "tag" is a list of string - elif index == 3 and isinstance(arg.value, ast.List): - tags = [] - for elt in arg.value.elts: - if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): - raise MalformedException(f"tag can only be List of string") - tags.append(elt.s) - self.cur_inline_test.tag = tags - # check if "disabled" is a boolean - elif index == 4 and isinstance(arg, ast.NameConstant) and isinstance(arg.value, bool): - self.cur_inline_test.disabled = arg.value - # check if "timeout" is a positive int - elif ( - index == 5 and isinstance(arg, ast.Num) and (isinstance(arg.n, float) or isinstance(arg.n, int)) - ): - if arg.n <= 0.0: - raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") - self.cur_inline_test.timeout = arg.n - else: - raise MalformedException( - f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive intege, 'tag' must be a list of string, 'timeout' must be a positive float" - ) - # keyword arguments - for keyword in node.keywords: - # check if "test_name" is a string - if ( - keyword.arg == self.arg_test_name_str - and isinstance(keyword.value, ast.Str) - and isinstance(keyword.value.s, str) - ): - self.cur_inline_test.test_name = keyword.value.s - # check if "parameterized" is a boolean - elif ( - keyword.arg == self.arg_parameterized_str - and isinstance(keyword.value, ast.NameConstant) - and isinstance(keyword.value.value, bool) - ): - self.cur_inline_test.parameterized = keyword.value.value - # check if "repeated" is a positive integer - elif ( - keyword.arg == self.arg_repeated_str - and isinstance(keyword.value, ast.Num) - and isinstance(keyword.value.n, int) - ): - if keyword.value.n <= 0.0: - raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - self.cur_inline_test.repeated = keyword.value.n - # check if "tag" is a list of string - elif keyword.arg == self.arg_tag_str and isinstance(keyword.value, ast.List): - tags = [] - for elt in keyword.value.elts: - if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): - raise MalformedException(f"tag can only be List of string") - tags.append(elt.s) - self.cur_inline_test.tag = tags - # check if "disabled" is a boolean - elif ( - keyword.arg == self.arg_disabled_str - and isinstance(keyword.value, ast.NameConstant) - and isinstance(keyword.value.value, bool) - ): - self.cur_inline_test.disabled = keyword.value.value - # check if "timeout" is a positive float - elif ( - keyword.arg == self.arg_timeout_str - and isinstance(keyword.value, ast.Num) - and (isinstance(keyword.value.n, float) or isinstance(keyword.value.n, int)) - ): - if keyword.value.n <= 0.0: - raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") - self.cur_inline_test.timeout = keyword.value.n - #keyword arg for devices - elif ( - keyword.arg == self.arg_devices_str - and isinstance(keyword.value, ast.List) - ): - devices = [] - for elt in keyword.value.elts: - if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): - raise MalformedException("devices can only be List of string") - if elt.s not in {"cpu", "cuda", "mps"}: - raise MalformedException(f"Invalid device: {elt.s}. Must be one of ['cpu', 'cuda', 'mps']") - devices.append(elt.s) - self.cur_inline_test.devices = devices - else: - raise MalformedException( - f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" - ) - else: - raise MalformedException(f"inline test: invalid {self.class_name_str}(), expected at most 3 args") + self.parse_constructor_args(node.args) + + #keyword arguments + keyword_args = [] + + #create list with 7 null values (for each position) + for i in range(0, NUM_OF_ARGUMENTS): + keyword_args.append(None) + + for keyword in node.keywords: + keyword_args[keyword_idxs[keyword.arg].value] = keyword.value + self.parse_constructor_args(keyword_args) if not self.cur_inline_test.test_name: @@ -664,81 +511,71 @@ class ConstrArgs(enum.Enum): ConstrArgs.DEVICES : [str] } - keyword_idxs = { - self.arg_test_name_str : ConstrArgs.TEST_NAME, - self.arg_parameterized_str : ConstrArgs.PARAMETERIZED, - self.arg_repeated_str : ConstrArgs.REPEATED, - self.arg_tag_str : ConstrArgs.TAG_STR, - self.arg_disabled_str : ConstrArgs.DISABLED, - self.arg_timeout_str : ConstrArgs.TIMEOUT, - self.arg_devices_str : ConstrArgs.DEVICES - } NUM_OF_ARGUMENTS = 7 - if sys.version_info >= (3, 8, 0): - # Arguments organized by expected ast type, value type, and index in that order - for index, arg in enumerate(args): - # Skips over null arguments; needed for keywords - if arg == None: - continue + # Arguments organized by expected ast type, value type, and index in that order + for index, arg in enumerate(args): + # Skips over null arguments; needed for keywords + if arg == None: + continue + + # Devices are not referenced in versions before 3.8; all other arguments can be from any version + if index == ConstrArgs.DEVICES and isinstance(arg, ast.List): + devices = [] + for elt in arg.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException("devices can only be List of string") + if elt.value not in {"cpu", "cuda", "mps"}: + raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") + devices.append(elt.value) + self.cur_inline_test.devices = devices + # Assumes version is past 3.8, no explicit references to ast.Constant before 3.8 + else: + corr_arg_type = False + corr_val_type = False + value_prop_name = "" + arg_idx = ConstrArgs(index) + + if sys.version_info >= (3, 8, 0) and isinstance(arg, expected_ast_arg_type[arg_idx]): + corr_arg_type = True + value_prop_name = "value" + elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[arg_idx]): + corr_arg_type = True + value_prop_name = pre_38_val_names[arg_idx] - # Devices are not referenced in versions before 3.8; all other arguments can be from any version - if index == ConstrArgs.DEVICES and isinstance(arg, ast.List): - devices = [] - for elt in arg.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException("devices can only be List of string") - if elt.value not in {"cpu", "cuda", "mps"}: - raise MalformedException(f"Invalid device: {elt.value}. Must be one of ['cpu', 'cuda', 'mps']") - devices.append(elt.value) - self.cur_inline_test.devices = devices - # Assumes version is past 3.8, no explicit references to ast.Constant before 3.8 + # Verifies value types; skipped for ast node types with no nested values + for arg_type in expected_ast_val_args[arg_idx]: + if arg_type == None: + corr_val_type = True + break + if isinstance(arg.value, arg_type): + corr_val_type = True + break + + if corr_val_type and corr_arg_type: + # Accounts for additional checks for REPEATED and TAG_STR arguments + match arg_idx: + case ConstrArgs.REPEATED: + if arg.value <= 0: + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") + self.cur_inline_test.repeated = getattr(arg, value_prop_name) + case ConstrArgs.TAG_STR: + tags = [] + for elt in arg.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException(f"tag can only be List of string") + tags.append(getattr(elt, value_prop_name)) + self.cur_inline_test.tag = tags + # For non-special cases, set the attribute defined by the dictionary + case _: + setattr(self.cur_inline_test, + property_names[arg_idx], + getattr(arg, value_prop_name)) else: - corr_arg_type = False - corr_val_type = False - value_prop_name = "" - arg_idx = ConstrArgs(index) - - if sys.version_info >= (3, 8, 0) and isinstance(arg, expected_ast_arg_type[arg_idx]): - corr_arg_type = True - value_prop_name = "value" - elif sys.version_info < (3, 8, 0) and isinstance(arg, pre_38_expec_ast_arg_type[arg_idx]): - corr_arg_type = True - value_prop_name = pre_38_val_names[arg_idx] - - # Verifies value types; skipped for ast node types with no nested values - for arg_type in expected_ast_val_args[arg_idx]: - if arg_type == None: - corr_val_type = True - break - if isinstance(arg.value, arg_type): - corr_val_type = True - break - - if corr_val_type and corr_arg_type: - # Accounts for additional checks for REPEATED and TAG_STR arguments - match arg_idx: - case ConstrArgs.REPEATED: - if arg.value <= 0: - raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - self.cur_inline_test.repeated = getattr(arg, value_prop_name) - case ConstrArgs.TAG_STR: - tags = [] - for elt in arg.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException(f"tag can only be List of string") - tags.append(getattr(elt, value_prop_name)) - self.cur_inline_test.tag = tags - # For non-special cases, set the attribute defined by the dictionary - case _: - setattr(self.cur_inline_test, - property_names[arg_idx], - getattr(arg, value_prop_name)) - else: - raise MalformedException( - f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" - ) - #raise MalformedException("Argument " + str(index) + " incorrectly formatted. Argument should be a " + ConstrArgs.expected_ast_val_args[index].type()) + raise MalformedException( + f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" + ) + #raise MalformedException("Argument " + str(index) + " incorrectly formatted. Argument should be a " + ConstrArgs.expected_ast_val_args[index].type()) def parameterized_inline_tests_init(self, node: ast.List): if not self.cur_inline_test.parameterized_inline_tests: From 10ac458e8a75f9cb492cff7a228208c39a87b2ec Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 13:34:28 -0500 Subject: [PATCH 23/28] Disabled Spyder Testing Code Snippet --- tests/test_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index fb28c59..113290a 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3,8 +3,8 @@ import pytest # For testing in Spyder only -if __name__ == "__main__": - pytest.main(['-v', '-s']) +# if __name__ == "__main__": +# pytest.main(['-v', '-s']) # pytest -p pytester From 97274d0f4063b10d6bf116d7368dd1bbbf1ca4f9 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 13:42:24 -0500 Subject: [PATCH 24/28] Removed Match Statements Apparently, they are incompatible with pre Python 3.10 --- src/inline/plugin.py | 78 +++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 0638c82..3bd525c 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -554,23 +554,42 @@ class ConstrArgs(enum.Enum): if corr_val_type and corr_arg_type: # Accounts for additional checks for REPEATED and TAG_STR arguments - match arg_idx: - case ConstrArgs.REPEATED: - if arg.value <= 0: - raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") - self.cur_inline_test.repeated = getattr(arg, value_prop_name) - case ConstrArgs.TAG_STR: - tags = [] - for elt in arg.elts: - if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): - raise MalformedException(f"tag can only be List of string") - tags.append(getattr(elt, value_prop_name)) - self.cur_inline_test.tag = tags - # For non-special cases, set the attribute defined by the dictionary - case _: - setattr(self.cur_inline_test, - property_names[arg_idx], - getattr(arg, value_prop_name)) + if arg_idx == ConstrArgs.REPEATED: + if arg.value <= 0: + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") + self.cur_inline_test.repeated = getattr(arg, value_prop_name) + elif arg_idx == ConstrArgs.TAG_STR: + tags = [] + for elt in arg.elts: + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException(f"tag can only be List of string") + tags.append(getattr(elt, value_prop_name)) + self.cur_inline_test.tag = tags + # For non-special cases, set the attribute defined by the dictionary + else: + setattr(self.cur_inline_test, + property_names[arg_idx], + getattr(arg, value_prop_name)) + + + + # match arg_idx: + # case ConstrArgs.REPEATED: + # if arg.value <= 0: + # raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") + # self.cur_inline_test.repeated = getattr(arg, value_prop_name) + # case ConstrArgs.TAG_STR: + # tags = [] + # for elt in arg.elts: + # if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + # raise MalformedException(f"tag can only be List of string") + # tags.append(getattr(elt, value_prop_name)) + # self.cur_inline_test.tag = tags + # # For non-special cases, set the attribute defined by the dictionary + # case _: + # setattr(self.cur_inline_test, + # property_names[arg_idx], + # getattr(arg, value_prop_name)) else: raise MalformedException( f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" @@ -1243,15 +1262,22 @@ def parse_inline_test(self, node): for call in inline_test_calls[inline_test_call_index:]: if isinstance(call.func, ast.Attribute): - match call.func.attr: - # "given(a, 1)" - case self.given_str: - self.parse_given(call) - inline_test_call_index += 1 - # "diff_given(devices, ["cpu", "cuda"])" - case self.diff_given_str: - self.parse_diff_given(call) - inline_test_call_index += 1 + if call.func.attr == self.given_str: + self.parse_given(call) + inline_test_call_index += 1 + elif call.func.attr == self.diff_given_str: + self.parse_diff_given(call) + inline_test_call_index += 1 + + # match call.func.attr: + # # "given(a, 1)" + # case self.given_str: + # self.parse_given(call) + # inline_test_call_index += 1 + # # "diff_given(devices, ["cpu", "cuda"])" + # case self.diff_given_str: + # self.parse_diff_given(call) + # inline_test_call_index += 1 else: break From 52905407ef687f8785a7b9491dca543ee10418f1 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 13:45:13 -0500 Subject: [PATCH 25/28] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 95f8b65..c59ccc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,4 +88,4 @@ select = [ ] [tool.ruff.lint.isort] -known-first-party = ["inline"] +known-first-party = ["inline"] \ No newline at end of file From ebd9b6daa45caca704859a839b1f268bb3ff0309 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 13:57:38 -0500 Subject: [PATCH 26/28] Hotfix --- src/inline/plugin.py | 2 +- tests/test_plugin.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 3bd525c..df98084 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -443,7 +443,7 @@ def parse_constructor(self, node): keyword_args.append(None) for keyword in node.keywords: - keyword_args[keyword_idxs[keyword.arg].value] = keyword.value + keyword_args[keyword_idxs[keyword.arg]] = keyword.value self.parse_constructor_args(keyword_args) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 113290a..fb28c59 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3,8 +3,8 @@ import pytest # For testing in Spyder only -# if __name__ == "__main__": -# pytest.main(['-v', '-s']) +if __name__ == "__main__": + pytest.main(['-v', '-s']) # pytest -p pytester From 69b5fd7a23aa24ebe015f14fc03761bed08fdf06 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 14:53:29 -0500 Subject: [PATCH 27/28] Changed Import Test to Standard Library --- tests/test_plugin.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index fb28c59..8fe88a6 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -30,11 +30,11 @@ def test_inline_detects_imports(self, pytester: Pytester): checkfile = pytester.makepyfile( """ from inline import itest - import numpy as np + import datetime def m(a): - b = np.arccos(a) - itest().given(a, -1).check_eq(b, np.pi) + b = a + datetime.timedelta(days=365) + itest().given(a, datetime.timedelta(days=1)).check_eq(b, datetime.timedelta(days=366)) """ ) for x in (pytester.path, checkfile): @@ -43,23 +43,23 @@ def m(a): res = pytester.runpytest() assert res.ret != 1 - def test_inline_detects_from_imports(self, pytester: Pytester): - checkfile = pytester.makepyfile( - """ - from inline import itest - import numpy as np - from scipy import stats as st + # def test_inline_detects_from_imports(self, pytester: Pytester): + # checkfile = pytester.makepyfile( + # """ + # from inline import itest + # import numpy as np + # from scipy import stats as st - def m(n, p): - b = st.binom(n, p) - itest().given(n, 100).given(p, 0.5).check_eq(b.mean(), n * p) - """ - ) - for x in (pytester.path, checkfile): - items, reprec = pytester.inline_genitems(x) - assert len(items) == 1 - res = pytester.runpytest() - assert res.ret == 0 + # def m(n, p): + # b = st.binom(n, p) + # itest().given(n, 100).given(p, 0.5).check_eq(b.mean(), n * p) + # """ + # ) + # for x in (pytester.path, checkfile): + # items, reprec = pytester.inline_genitems(x) + # assert len(items) == 1 + # res = pytester.runpytest() + # assert res.ret == 0 def test_inline_parser(self, pytester: Pytester): checkfile = pytester.makepyfile( From 0417aa5e4a840b35787a651fd11c85329c050be8 Mon Sep 17 00:00:00 2001 From: hanse141 Date: Fri, 14 Nov 2025 15:00:00 -0500 Subject: [PATCH 28/28] Create 11-14 Meeting Notes.txt --- meeting-notes/11-14 Meeting Notes.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 meeting-notes/11-14 Meeting Notes.txt diff --git a/meeting-notes/11-14 Meeting Notes.txt b/meeting-notes/11-14 Meeting Notes.txt new file mode 100644 index 0000000..74c122d --- /dev/null +++ b/meeting-notes/11-14 Meeting Notes.txt @@ -0,0 +1,16 @@ +Split progress so far, and push each as a separate request: +- Constructor +- Imports +- Diff Given + +-Start another fork on the original pytest-inline repo +-Put constructor changes onto new fork +-Push request +-Repeat for other two components + +Diff Given: +- Multiple scenes to worry about +- May need to up the best version, then develop off that + +Clean up as separate requests, push them, then add more concrete tests for more inline tests +- More tests from constructor to inline \ No newline at end of file