From c131f6e3b29daf8e0bece58d5b77783d5bde86c6 Mon Sep 17 00:00:00 2001 From: Tim Hunter Date: Sun, 7 Mar 2021 14:34:45 +0100 Subject: [PATCH 1/2] tests that show issue --- dds/_introspect_indirect.py | 3 ++- dds/introspect.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/dds/_introspect_indirect.py b/dds/_introspect_indirect.py index f3c2b14..5ae60b3 100644 --- a/dds/_introspect_indirect.py +++ b/dds/_introspect_indirect.py @@ -29,6 +29,7 @@ ExternalVarsVisitor, LocalVar, _function_name, + read_source, ) from .structures import ( FunctionArgContext, @@ -126,7 +127,7 @@ def _introspect_fun( fiis_ = gctx.cached_indirect_interactions.get(fun_path) if fiis_ is not None: return fiis_ - src = inspect.getsource(f) + src = read_source(f) # _logger.debug(f"Starting _introspect: {f}: src={src}") ast_src = ast.parse(src) ast_f = ast_src.body[0] # type: ignore diff --git a/dds/introspect.py b/dds/introspect.py index 007592c..df8b99f 100644 --- a/dds/introspect.py +++ b/dds/introspect.py @@ -106,7 +106,7 @@ def _introspect_class( fis_ = gctx.cached_fun_interactions.get(fis_key) if fis_ is not None: return fis_ - src = inspect.getsource(c) + src = read_source(c) # _logger.debug(f"Starting _introspect_class: {c}: src={src}") ast_src = ast.parse(src) ast_f: ast.ClassDef = ast_src.body[0] # type: ignore @@ -191,7 +191,7 @@ def _introspect_fun( ast_f: Union[ast.Lambda, ast.FunctionDef] if is_lambda(f): # _logger.debug(f"_introspect: is_lambda: {f}") - src = inspect.getsource(f) + src = read_source(f) h = dds_hash(src) # Have a stable name for the lambda function fun_path = CanonicalPath( @@ -211,7 +211,7 @@ def _introspect_fun( fis_ = gctx.cached_fun_interactions.get(fis_key) if fis_ is not None: return fis_ - src = inspect.getsource(f) + src = read_source(f) # _logger.debug(f"Starting _introspect: {f}: src={src}") ast_src = ast.parse(src) ast_f = ast_src.body[0] # type: ignore @@ -882,6 +882,16 @@ def _build_return_sig( return dds_hash_commut(all_pairs) +def read_source(f: Callable[..., Any]) -> str: + # For functions defined in test modules, the source code is directly + # attached to the function. + if "__dds_source" in dir(f): + src: str = f.__dds_source # type: ignore + return src + else: + return inspect.getsource(f) + + _accepted_packages: Set[Package] = { Package("dds"), Package("__main__"), From a4028e8c8d3e73c5232a470d63b5ccc7e094934f Mon Sep 17 00:00:00 2001 From: Tim Hunter Date: Sun, 7 Mar 2021 14:46:35 +0100 Subject: [PATCH 2/2] tests that show issue --- dds_tests/test_block_boundary.py | 128 +++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 dds_tests/test_block_boundary.py diff --git a/dds_tests/test_block_boundary.py b/dds_tests/test_block_boundary.py new file mode 100644 index 0000000..7c84152 --- /dev/null +++ b/dds_tests/test_block_boundary.py @@ -0,0 +1,128 @@ +import dds +import pytest +from .utils import cleandir, Counter + +_ = cleandir + +_var_function = {} + + +def _dummy(): + pass + + +def _eval_function(s, function_name, dct): + global _var_function + _var_function["key"] = None + s2 = ( + s + + f""" + +global _var_function +_var_function["key"] = {function_name} + """ + ) + d = {"_var_function": _var_function, "dds": dds} + d.update(dct) + exec(s2, d) + + f = _var_function["key"] + f.__dds_source = s + f.__module__ = _dummy.__module__ + globals()[function_name] = f + return f + + +_c = Counter() + + +def f(i): + _ = i + _c.increment() + return 1 + + +subsequent_change_s1 = """ +def function(): + i = 1 + _ = dds.keep("/p", f, i) + # a +""" + +subsequent_change_s2 = """ +def function(): + i = 1 + _ = dds.keep("/p", f, i) + # b +""" + + +@pytest.mark.usefixtures("cleandir") +def test_subsequent_change(): + fun = _eval_function(subsequent_change_s1, "function", {"f": f}) + dds.eval(fun) + assert _c.value == 1 + fun = _eval_function(subsequent_change_s2, "function", {"f": f}) + dds.eval(fun) + assert _c.value == 2 + + +next_line_change_s1 = """ +def function(): + i = 1 + _ = dds.keep("/p", f, i) + + # a +""" + +next_line_change_s2 = """ +def function(): + i = 1 + _ = dds.keep("/p", f, i) + + # b +""" + + +@pytest.mark.usefixtures("cleandir") +def test_next_line_change(): + _c.value = 0 + fun = _eval_function(next_line_change_s1, "function", {"f": f}) + dds.eval(fun) + assert _c.value == 1 + fun = _eval_function(next_line_change_s2, "function", {"f": f}) + dds.eval(fun) + assert _c.value == 1 + + +subexpression_s1 = """ +def function(): + i = 1 + _ = [ + dds.keep("/p", f, i) + for x in range(3) + if x < 10 + ] +""" + +subexpression_s2 = """ +def function(): + i = 1 + _ = [ + dds.keep("/p", f, i) + for x in range(3) + if x < 3 # change + ] +""" + + +@pytest.mark.usefixtures("cleandir") +def test_subexpression(): + _c.value = 0 + fun = _eval_function(subexpression_s1, "function", {"f": f}) + dds.eval(fun) + assert _c.value == 1 + fun = _eval_function(subexpression_s2, "function", {"f": f}) + dds.eval(fun) + # TODO: should be 2 + assert _c.value == 1