def test_strip_indent(self, find_indent_mock): find_indent_mock.return_value = 4 indented_source = """ def foo(): x = 3 y = 4 # Comment here if x == 3: y = 5 return x + y """ stripped_source = """ def foo(): x = 3 y = 4 # Comment here if x == 3: y = 5 return x + y """ self.assertEqual(strip_indent(indented_source), stripped_source, "Incorrectly stripped indentation.")
def _record(f): """Transforms `f` such that after every line record_state is called. *** HERE BE DRAGONS *** """ global num_fns_recorded # Make sure this is not a recursive decorator application. global _blocked if _blocked: return f # We only support recording one fn's executions at the moment. if num_fns_recorded: raise ValueError( 'Cannot `record` more than one function at a time.') num_fns_recorded += 1 source = inspect.getsource(f) parsed = ast.parse(strip_indent(source)) original_body = list(parsed.body[0].body) # Update body parsed.body[0].body = _fill_body_with_record(original_body) # Compile and inject modified function back into its env. new_f_compiled = compile(parsed, '<string>', 'exec') env = sys.modules[f.__module__].__dict__ # We also need to inject our stuff in there. env[RECORD_FN_NAME] = globals()[RECORD_FN_NAME] _blocked = True exec new_f_compiled in env _blocked = False # Keep a reference to the (original) mangled function, because our decorator # will end up replacing it with `wrapped`. Then, whenever `wrapped` ends up # calling the original function, it would end up calling itself, leading # to an infinite recursion. Thus, we keep the fn we want to call under # a separate key which `wrapped` can call without a problem. # We are doing this instead of simply changing the recorded fn's name because # we have to support recursive calls (which would lead to NameError if we changed # the fn's name). env[MANGLED_FN_NAME] = env[f.__name__] init_recorded_state() file, path = _get_dump_file() logger.info( "Will record execution of %s in %s . " "Use `view_trace %s` to view it.", f.__name__, path, path) # Wrap in our own function such that we can dump the recorded state at the end. @wraps(f) def wrapped(*args, **kwargs): # Write source to file the first time we are called. global first_dump_call if first_dump_call: dump_fn_source(file, source) first_dump_call = False global num_recorded_executions # Are we still recording? if num_recorded_executions < num_executions: # Clear state for new run. init_recorded_state() ret = env[MANGLED_FN_NAME](*args, **kwargs) dump_recorded_state(file) num_recorded_executions += 1 # If not, just call the original function. else: ret = f(*args, **kwargs) return ret return wrapped
def _record(f): """Transforms `f` such that after every line record_state is called. *** HERE BE DRAGONS *** """ global num_fns_recorded # Make sure this is not a recursive decorator application. global _blocked if _blocked: return f # We only support recording one fn's executions at the moment. if num_fns_recorded: raise ValueError('Cannot `record` more than one function at a time.') num_fns_recorded += 1 source = inspect.getsource(f) parsed = ast.parse(strip_indent(source)) original_body = list(parsed.body[0].body) # Update body parsed.body[0].body = _fill_body_with_record(original_body) # Compile and inject modified function back into its env. new_f_compiled = compile(parsed, '<string>', 'exec') env = sys.modules[f.__module__].__dict__ # We also need to inject our stuff in there. env[RECORD_FN_NAME] = globals()[RECORD_FN_NAME] _blocked = True exec new_f_compiled in env _blocked = False # Keep a reference to the (original) mangled function, because our decorator # will end up replacing it with `wrapped`. Then, whenever `wrapped` ends up # calling the original function, it would end up calling itself, leading # to an infinite recursion. Thus, we keep the fn we want to call under # a separate key which `wrapped` can call without a problem. # We are doing this instead of simply changing the recorded fn's name because # we have to support recursive calls (which would lead to NameError if we changed # the fn's name). env[MANGLED_FN_NAME] = env[f.__name__] init_recorded_state() file, path = _get_dump_file() logger.info("Will record execution of %s in %s . " "Use `view_trace %s` to view it.", f.__name__, path, path) # Wrap in our own function such that we can dump the recorded state at the end. @wraps(f) def wrapped(*args, **kwargs): # Write source to file the first time we are called. global first_dump_call if first_dump_call: dump_fn_source(file, source) first_dump_call = False global num_recorded_executions # Are we still recording? if num_recorded_executions < num_executions: # Clear state for new run. init_recorded_state() ret = env[MANGLED_FN_NAME](*args, **kwargs) dump_recorded_state(file) num_recorded_executions += 1 # If not, just call the original function. else: ret = f(*args, **kwargs) return ret return wrapped