diff --git a/osbot_utils/base_classes/Kwargs_To_Self.py b/osbot_utils/base_classes/Kwargs_To_Self.py index 909d9731..2647c65c 100644 --- a/osbot_utils/base_classes/Kwargs_To_Self.py +++ b/osbot_utils/base_classes/Kwargs_To_Self.py @@ -65,6 +65,8 @@ class MyConfigurableClass(Kwargs_To_Self): that are already defined in the class to be set. """ + __lock_attributes__ = False + def __init__(self, **kwargs): """ Initialize an instance of the derived class, strictly assigning provided keyword @@ -92,6 +94,12 @@ def __init__(self, **kwargs): def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass + def __setattr__(self, name, value): + if self.__lock_attributes__: + if not hasattr(self, name): + raise AttributeError(f"'[Object Locked] Current object is locked (with __lock_attributes__=True) which prenvents new attributes allocations (i.e. setattr calls). In this case {type(self).__name__}' object has no attribute '{name}'") from None + super().__setattr__(name, value) + @classmethod def __cls_kwargs__(cls): """Return current class dictionary of class level variables and their values.""" @@ -162,3 +170,13 @@ def __locals__(self): if not isinstance(v, types.FunctionType): kwargs[k] = v return kwargs + + def locked(self, value=True): + self.__lock_attributes__ = value + return self + + def update_from_kwargs(self, **kwargs): + """Update instance attributes with values from provided keyword arguments.""" + for key, value in kwargs.items(): + setattr(self, key, value) + return self \ No newline at end of file diff --git a/osbot_utils/utils/trace/Trace_Call.py b/osbot_utils/utils/trace/Trace_Call.py index ba839076..4a0e5b65 100644 --- a/osbot_utils/utils/trace/Trace_Call.py +++ b/osbot_utils/utils/trace/Trace_Call.py @@ -13,22 +13,24 @@ def trace_calls(title=None, print_traces=True, show_locals=False, source_code=False, ignore=None, include=None, max_string=None, show_types=False, show_caller=False, show_class=False, show_path=False, show_duration=False, duration_bigger_than=0, contains=None, show_internals=False, - extra_data=False, show_lines=False, print_lines=False): + extra_data=False, show_lines=False, print_lines=False, enabled=True): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): config_kwargs = dict(title=title, print_traces_on_exit=print_traces, print_locals=show_locals, trace_capture_source_code=source_code, ignore_start_with=ignore, - capture_start_with=include, print_max_string_length=max_string, + trace_capture_start_with=include, print_max_string_length=max_string, show_parent_info=show_types, show_method_class=show_class, show_caller=show_caller, show_source_code_path=show_path, capture_duration=show_duration, print_duration= show_duration, with_duration_bigger_than=duration_bigger_than, trace_capture_contains=contains, trace_show_internals=show_internals, - capture_extra_data=extra_data, trace_capture_lines=show_lines, - print_lines_on_exit=print_lines) + capture_extra_data=extra_data, + print_lines_on_exit=print_lines, trace_enabled=enabled, + trace_capture_lines=show_lines or print_lines) + + config = Trace_Call__Config().locked().update_from_kwargs(**config_kwargs) - config = Trace_Call__Config(**config_kwargs) with Trace_Call(config=config): result = func(*args, **kwargs) return result @@ -46,7 +48,7 @@ def __init__(self, **kwargs): self.trace_call_print_traces = Trace_Call__Print_Traces(config=self.config) self.trace_call_view_model = Trace_Call__View_Model () self.config.print_traces_on_exit = self.config.print_traces_on_exit - self.config.trace_capture_start_with = self.config.capture_start_with or [] # todo add a better way to set these to [] when then value is null + #self.config.trace_capture_start_with = self.config.capture_start_with or [] # todo add a better way to set these to [] when then value is null self.config.trace_ignore_start_with = self.config.ignore_start_with or [] # probablty better done inside Kwargs_To_Self since it doesn't make sense for lists or dicts to have None value self.config.trace_capture_contains = self.config.trace_capture_contains or [] # and None will be quite common since we can use [] on method's params self.config.print_max_string_length = self.config.print_max_string_length or PRINT_MAX_STRING_LENGTH @@ -61,37 +63,39 @@ def __exit__(self, exc_type, exc_val, exc_tb): return self.on_exit() def on_enter(self): - self.start() # Start the tracing + if self.config.trace_enabled: + self.start() # Start the tracing return self def on_exit(self): - self.stop() # Stop the tracing - if self.config.print_traces_on_exit: - self.print() - if self.config.print_lines_on_exit: - self.print_lines() + if self.config.trace_enabled: + self.stop() # Stop the tracing + if self.config.print_traces_on_exit: + self.print() + if self.config.print_lines_on_exit: + self.print_lines() def capture_all(self): self.config.trace_capture_all = True return self - def create_view_model(self): + def view_data(self): return self.trace_call_view_model.create(self.stack) def print(self): - view_model = self.create_view_model() + view_model = self.view_data() self.trace_call_print_traces.print_traces(view_model) #self.print_lines() return view_model def print_lines(self): print() - view_model = self.create_view_model() + view_model = self.view_data() print_lines = Trace_Call__Print_Lines(config=self.config, view_model=view_model) print_lines.print_lines() def start(self): - self.trace_call_handler.stack.add_node(title=self.trace_call_handler.trace_title) + self.trace_call_handler.stack.add_node(title=self.trace_call_handler.config.title) self.prev_trace_function = sys.gettrace() sys.settrace(self.trace_call_handler.trace_calls) # Set the new trace function @@ -101,3 +105,7 @@ def stop(self): def stats(self): return self.trace_call_handler.stats + + def stats_data(self): + return self.trace_call_handler.stats.raw_call_stats + diff --git a/osbot_utils/utils/trace/Trace_Call__Config.py b/osbot_utils/utils/trace/Trace_Call__Config.py index 846e29b5..db4981bd 100644 --- a/osbot_utils/utils/trace/Trace_Call__Config.py +++ b/osbot_utils/utils/trace/Trace_Call__Config.py @@ -1,3 +1,5 @@ +from osbot_utils.utils.Dev import pprint + from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self PRINT_MAX_STRING_LENGTH = 100 @@ -9,8 +11,8 @@ class Trace_Call__Config(Kwargs_To_Self): capture_extra_data : bool capture_frame : bool = True capture_frame_stats : bool + deep_copy_locals : bool trace_capture_lines : bool - capture_start_with : list ignore_start_with : list print_duration : bool print_max_string_length : int = PRINT_MAX_STRING_LENGTH @@ -25,6 +27,38 @@ class Trace_Call__Config(Kwargs_To_Self): trace_capture_source_code : bool trace_capture_start_with : list trace_capture_contains : list + trace_enabled : bool = True trace_show_internals : bool trace_ignore_start_with : list - with_duration_bigger_than : bool \ No newline at end of file + with_duration_bigger_than : bool + + def all(self, print_traces=True): + self.trace_capture_all = True + self.print_traces_on_exit = print_traces + + def capture(self, starts_with=None, contains=None, ignore=None): + if starts_with: + if type(starts_with) is str: + starts_with = [starts_with] + self.trace_capture_start_with = starts_with + if contains: + if type(contains) is str: + contains = [contains] + self.trace_capture_contains = contains + if ignore: + if type(ignore) is str: + ignore = [ignore] + self.ignore_start_with = ignore + self.print_traces_on_exit = True + + def locals(self): + self.capture_locals = True + self.print_locals = True + + def lines(self, print_traces=True, print_lines=True): + self.trace_capture_lines = True + self.print_traces_on_exit = print_traces + self.print_lines_on_exit = print_lines + + def print_config(self): + pprint(self.__locals__()) \ No newline at end of file diff --git a/osbot_utils/utils/trace/Trace_Call__Handler.py b/osbot_utils/utils/trace/Trace_Call__Handler.py index 7f06a7e9..b5156d8c 100644 --- a/osbot_utils/utils/trace/Trace_Call__Handler.py +++ b/osbot_utils/utils/trace/Trace_Call__Handler.py @@ -21,11 +21,11 @@ class Trace_Call__Handler(Kwargs_To_Self): def __init__(self, **kwargs): super().__init__(**kwargs) - self.trace_title = self.config.title or DEFAULT_ROOT_NODE_NODE_TITLE # Title for the trace root node + self.config.title = self.config.title or DEFAULT_ROOT_NODE_NODE_TITLE # Title for the trace root node self.stack.config = self.config def add_default_root_node(self): - return self.stack.add_node(title=self.trace_title) + return self.stack.add_node(title=self.config.title) def add_line(self, frame): if self.config.trace_capture_lines: @@ -43,25 +43,36 @@ def add_line(self, frame): return False def add_line_to_node(self, frame, target_node, event): + def source_code_for_frame(function_name): + try: + return inspect.getsource(frame.f_code) + except Exception as error: + return '' + #print(self.stack.line_index, f'def {function_name}() : error] {error}' ) + #return f'def {function_name}() : [error] {error}' + if frame and target_node: func_name = frame.f_code.co_name module = frame.f_globals.get("__name__", "") self_local = class_full_name(frame.f_locals.get('self')) stack_size = len(self.stack) + line ='' + line_number=0 if event == 'call': # if this is a call we need to do the code below to get the actual method signature (and decorators) function_name = frame.f_code.co_name filename = frame.f_code.co_filename # Get the filename where the function is defined start_line_number = frame.f_code.co_firstlineno # Get the starting line number - source_lines = inspect.getsource(frame.f_code).split('\n') - def_line_number = start_line_number # Try to find the actual 'def' line - for line in source_lines: - if line.strip().startswith('def ' + function_name): - break - def_line_number += 1 - else: - def_line_number = start_line_number # If the 'def' line wasn't found, default to the starting line - line_number = def_line_number - line = linecache.getline(filename, line_number).rstrip() # todo: refactor this to not capture this info here, and to use the Ast_* utils to get a better source code mapping + source_lines = source_code_for_frame(function_name).split('\n') + if source_lines: + def_line_number = start_line_number # Try to find the actual 'def' line + for line in source_lines: + if line.strip().startswith('def ' + function_name): + break + def_line_number += 1 + else: + def_line_number = start_line_number # If the 'def' line wasn't found, default to the starting line + line_number = def_line_number + line = linecache.getline(filename, line_number).rstrip() # todo: refactor this to not capture this info here, and to use the Ast_* utils to get a better source code mapping else: filename = frame.f_code.co_filename # get the filename line_number = frame.f_lineno # get the current line number @@ -75,6 +86,8 @@ def add_line_to_node(self, frame, target_node, event): stack_size=stack_size) target_node.lines.append(line_data) return True + # else: + # print(f'no line for : {self.stack.line_index}, {module}.{func_name}') return False def add_frame(self, frame): @@ -93,6 +106,8 @@ def handle_event__call(self, frame): if self.config.trace_capture_lines: self.add_line_to_node(frame, new_node,'call') return new_node + else: + self.stats.calls_skipped += 1 def handle_event__line(self, frame): return self.add_line(frame) @@ -150,17 +165,17 @@ def stack_top(self): def trace_calls(self, frame, event, arg): if event == 'call': - self.stats.event_call +=1 + self.stats.calls +=1 self.handle_event__call(frame) # todo: handle bug with locals which need to be serialised, since it's value will change elif event == 'return': - self.stats.event_return += 1 + self.stats.returns += 1 self.handle_event__return(frame, arg) elif event == 'exception': - self.stats.event_exception +=1 # for now don't handle exception events + self.stats.exceptions +=1 # for now don't handle exception events elif event == 'line': self.handle_event__line(frame) - self.stats.event_line +=1 + self.stats.lines +=1 else: - self.stats.event_unknown += 1 + self.stats.unknowns += 1 return self.trace_calls diff --git a/osbot_utils/utils/trace/Trace_Call__Print_Lines.py b/osbot_utils/utils/trace/Trace_Call__Print_Lines.py index e4bada9b..08a526ea 100644 --- a/osbot_utils/utils/trace/Trace_Call__Print_Lines.py +++ b/osbot_utils/utils/trace/Trace_Call__Print_Lines.py @@ -48,40 +48,43 @@ def max_fields_value(self, items, *fields): return max_value def print_lines(self, ): - lines = self.lines() + lines = self.lines() + print("--------- CALL TRACER (Lines)----------") + print(f"Here are the {len(lines)} lines captured\n") + max_length__sig = self.max_fields_length(lines, 'module', 'func_name') + 2 max_length__line = self.max_fields_length(lines, 'line' ) + self.max_fields_value (lines, 'stack_size' ) + 5 # this + 5 helps with the alignment of the larger line (so that it doesn't overflow the table) max_length__self = self.max_fields_length(lines, 'self_local' ) - print( '┌─────┬─' + '─' * max_length__line +'──┬─' + '─' * max_length__sig + '─┬─' + '─' * max_length__self + '─┬──────┬───────┐ ') - print(f"│ # │ {'Source code':<{max_length__line}} │ {'Method Class and Name':<{max_length__sig}} │ {'Self object':<{max_length__self}} │ Line │ Depth │ ") - print( '├─────┼─' + '─' * max_length__line +'──┼─' + '─'* max_length__sig + '─┼─' + '─' * max_length__self + '─┼──────┼───────┤ ') + print( '┌─────┬──────┬─' + '─' * max_length__line +'──┬─' + '─' * max_length__sig + '─┬─' + '─' * max_length__self + '─┬───────┐ ') + print(f"│ # │ Line │ {'Source code':<{max_length__line}} │ {'Method Class and Name':<{max_length__sig}} │ {'Self object':<{max_length__self}} │ Depth │ ") + print( '├─────┼──────┼─' + '─' * max_length__line +'──┼─' + '─'* max_length__sig + '─┼─' + '─' * max_length__self + '─┼───────┤ ') for line_data in lines: - index = line_data.get('index') - func_name = line_data.get('func_name') - line_number = line_data.get('line_number') - module = line_data.get('module') - event = line_data.get('event') - line = line_data.get('line') - self_local = line_data.get('self_local') or '' - method_sig = f"{module}.{func_name}" - stack_size = line_data.get('stack_size') -1 - - text_depth = f'{stack_size:5}' - text_depth_padding = ' ' * ((stack_size-1) * 2) - text_index = f'{text_grey(index):12}' - text_line_no = f'{line_number:4}' - text_method_sig = f'{method_sig:{max_length__sig}}' - - if event == 'call': - text_line = f'{text_bold_green(line)}' - else: - text_line = f'{text_light_grey(line)}' - - text_line_padding = ' ' * (max_length__line - ansi_text_visible_length(text_line) - len(text_depth_padding)) - text_source_code = f'{text_depth_padding}{text_line} {text_line_padding}' - - print(f"│ {text_index} │ {text_source_code} │ {text_method_sig} │ {self_local:<{max_length__self}} │ {text_line_no} │ {text_depth} │") - - print('└─────┴──' + '─' * max_length__line + '─┴─' + '─' * max_length__sig + '─┴──' + '─' * max_length__self + '┴──────┴───────┘') + index = line_data.get('index') + func_name = line_data.get('func_name') + line_number = line_data.get('line_number') + module = line_data.get('module') + event = line_data.get('event') + line = line_data.get('line') + self_local = line_data.get('self_local') or '' + method_sig = f"{module}.{func_name}" + stack_size = line_data.get('stack_size') -1 + + text_depth = f'{stack_size:5}' + text_depth_padding = ' ' * ((stack_size-1) * 2) + text_index = f'{text_grey(index):12}' + text_line_no = f'{line_number:4}' + text_method_sig = f'{method_sig:{max_length__sig}}' + + if event == 'call': + text_line = f'{text_bold_green(line)}' + else: + text_line = f'{text_light_grey(line)}' + + text_line_padding = ' ' * (max_length__line - ansi_text_visible_length(text_line) - len(text_depth_padding)) + text_source_code = f'{text_depth_padding}{text_line} {text_line_padding}' + + print(f"│ {text_index} │ {text_line_no} │ {text_source_code} │ {text_method_sig} │ {self_local:<{max_length__self}} │ {text_depth} │") + + print('└─────┴──────┴──' + '─' * max_length__line + '─┴─' + '─' * max_length__sig + '─┴──' + '─' * max_length__self + '┴───────┘') diff --git a/osbot_utils/utils/trace/Trace_Call__Stack.py b/osbot_utils/utils/trace/Trace_Call__Stack.py index c369e6ef..de48b945 100644 --- a/osbot_utils/utils/trace/Trace_Call__Stack.py +++ b/osbot_utils/utils/trace/Trace_Call__Stack.py @@ -1,5 +1,6 @@ import linecache import time +from copy import copy, deepcopy from osbot_utils.utils.Dev import pprint @@ -82,7 +83,13 @@ def create_stack_node(self, frame, full_name, source_code, call_index): if self.config.capture_frame: new_node.frame = frame if self.config.capture_locals: - new_node.locals = frame.f_locals + if self.config.deep_copy_locals: + try: + new_node.locals = deepcopy(frame.f_locals) + except Exception as error: + new_node.locals = {'error': f'error in deepcopy: {error}'} + else: + new_node.locals = frame.f_locals if self.config.capture_duration: new_node.call_start = time.perf_counter() return new_node diff --git a/osbot_utils/utils/trace/Trace_Call__Stack_Node.py b/osbot_utils/utils/trace/Trace_Call__Stack_Node.py index f0b60f65..0697b127 100644 --- a/osbot_utils/utils/trace/Trace_Call__Stack_Node.py +++ b/osbot_utils/utils/trace/Trace_Call__Stack_Node.py @@ -31,6 +31,12 @@ def __eq__(self, other): def __repr__(self): return f'Trace_Call__Stack_Node (call_index={self.call_index})' + def all_children(self): + all_children = self.children.copy() # Initialize the list with the current node's children + for child in self.children: # Recursively add the children of each child node + all_children.extend(child.all_children()) + return all_children + def info(self): return f'Stack_Node: call_index:{self.call_index} | name: {self.name} | children: {len(self.children)} | source_code: {self.source_code is not None}' diff --git a/osbot_utils/utils/trace/Trace_Call__Stats.py b/osbot_utils/utils/trace/Trace_Call__Stats.py index 56978348..77fc7cef 100644 --- a/osbot_utils/utils/trace/Trace_Call__Stats.py +++ b/osbot_utils/utils/trace/Trace_Call__Stats.py @@ -8,12 +8,13 @@ class Trace_Call__Stats(Kwargs_To_Self): - event_call : int - event_exception : int - event_line : int - event_return : int - event_unknown : int # to use for extra events that are not being captured - raw_call_stats : list + calls : int + calls_skipped : int + exceptions : int + lines : int + returns : int + unknowns : int # to use for extra events that are not being captured + raw_call_stats : list def __repr__(self): return str(self.stats()) diff --git a/tests/utils/trace/test_Trace_Call.py b/tests/utils/trace/test_Trace_Call.py index 9c097912..fe680725 100644 --- a/tests/utils/trace/test_Trace_Call.py +++ b/tests/utils/trace/test_Trace_Call.py @@ -57,7 +57,7 @@ def test___exit__(self): self.trace_call.__exit__(None, None, None) mock_stop.assert_called_with() - self.trace_call.create_view_model() # this is populated by the self.trace_view_model.view_model object + self.trace_call.view_data() # this is populated by the self.trace_view_model.view_model object assert self.trace_view_model.view_model == [{ 'duration': 0.0, 'extra_data': {}, 'lines': [],'prefix': '', 'tree_branch': '─── ', 'emoji': '📦 ', 'method_name': '', 'method_parent': '', 'parent_info': '', 'locals': {}, 'source_code': '', @@ -129,7 +129,7 @@ def test___enter__exit__(self, builtins_print): view_model = trace_call.trace_call_view_model.view_model assert len(view_model) == 4, "Four function calls should be traced" - assert view_model[0]['method_name'] == handler.trace_title , "First function in view_model should be 'traces'" + assert view_model[0]['method_name'] == handler.config.title , "First function in view_model should be 'traces'" assert view_model[1]['method_name'] == 'dummy_function' , "2nd function in view_model should be 'dummy_function'" assert view_model[2]['method_name'] == 'another_function' , "3rd function in view_model should be 'another_function'" assert view_model[3]['method_name'] == 'dummy_function' , "4th function in view_model should be 'dummy_function'" @@ -143,15 +143,6 @@ def test___enter__exit__(self, builtins_print): call('\x1b[1m│ └── 🔗️ another_function\x1b[0m test_Trace_Call'), call('\x1b[1m└────────── 🧩️ dummy_function\x1b[0m test_Trace_Call')] != [] - # todo: figure out why we are getting these two different values (including the case with pycharm) - if in_github_action(): - assert trace_call.trace_call_handler.stats == Trace_Call__Stats(event_call=6, event_line=9, event_return=3) - else: - if 'PYCHARM_RUN_COVERAGE' in os.environ: - event_line = 9 - else: - event_line = 8 - assert trace_call.trace_call_handler.stats == Trace_Call__Stats(event_call=6, event_line=event_line, event_return=3) def test__check_that_stats_catches_exception_stats(self): try: @@ -166,24 +157,6 @@ def throw_an_exception(): except Exception as error: assert error.args[0] == 'test_2' - - if 'PYCHARM_RUN_COVERAGE' in os.environ: # handle the situation where we are getting differnt values deppending if running the test directly on under 'coverage' api - event_line = 5 - event_return = 1 - else: - event_line = 4 - event_return = 1 - - # todo: figure out why we are getting these two different values - if in_github_action(): - event_line = 5 - event_return = 1 - - assert trace_call.stats() == Trace_Call__Stats(event_call = 4 , - event_exception = 1 , - event_line = event_line , - event_return = event_return , - event_unknown = 0 ) def test__config_capture_frame_stats(self): self.config.capture_frame_stats = True self.config.show_parent_info = False @@ -196,24 +169,6 @@ def an_temp_file(): an_temp_file() - if in_github_action(): - expected_stats = dict(event_call = 98 , - event_exception = 4 , - event_line = 481 , - event_return = 95 , - event_unknown = 0 ) - else: - if 'PYCHARM_RUN_COVERAGE' in os.environ: - event_line = 513 - else: - event_line = 512 - expected_stats = dict(event_call = 98 , - event_exception = 4 , - event_line = event_line , - event_return = 95 , - event_unknown = 0 ) - assert self.trace_call.stats() == Trace_Call__Stats(**expected_stats) - with patch('builtins.print') as builtins_print: view_model = self.trace_view_model.create(self.trace_call.stack) self.trace_call.trace_call_print_traces.print_traces(view_model) diff --git a/tests/utils/trace/test_Trace_Call_Stats.py b/tests/utils/trace/test_Trace_Call_Stats.py index 8b74187a..9509b3d3 100644 --- a/tests/utils/trace/test_Trace_Call_Stats.py +++ b/tests/utils/trace/test_Trace_Call_Stats.py @@ -12,11 +12,12 @@ def setUp(self): self.trace_call_stats = Trace_Call__Stats() def test___kwargs__(self): - expected_locals = dict(event_call = 0 , - event_exception = 0 , - event_line = 0 , - event_return = 0 , - event_unknown = 0 , + expected_locals = dict(calls = 0 , + calls_skipped = 0, + exceptions = 0 , + lines = 0 , + returns = 0 , + unknowns = 0 , raw_call_stats = [] ) assert Trace_Call__Stats.__cls_kwargs__ () == expected_locals diff --git a/tests/utils/trace/test_Trace_Call__Config.py b/tests/utils/trace/test_Trace_Call__Config.py index 213f9aeb..78772500 100644 --- a/tests/utils/trace/test_Trace_Call__Config.py +++ b/tests/utils/trace/test_Trace_Call__Config.py @@ -18,7 +18,7 @@ def test__kwargs__(self): 'capture_frame' : True , 'capture_frame_stats' : False , 'capture_locals' : True , - 'capture_start_with' : [] , + 'deep_copy_locals' : False , 'ignore_start_with' : [] , 'print_locals' : False , 'print_max_string_length' : 100 , @@ -35,6 +35,7 @@ def test__kwargs__(self): 'trace_capture_source_code': False , 'trace_capture_start_with' : [] , 'trace_capture_contains' : [] , + 'trace_enabled' : True , 'trace_ignore_start_with' : [] , 'trace_show_internals' : False , 'with_duration_bigger_than': False } diff --git a/tests/utils/trace/test_Trace_Call__Handler.py b/tests/utils/trace/test_Trace_Call__Handler.py index 26565dd1..836fe50f 100644 --- a/tests/utils/trace/test_Trace_Call__Handler.py +++ b/tests/utils/trace/test_Trace_Call__Handler.py @@ -34,8 +34,8 @@ def test___default_kwargs(self): def test___init__(self): assert Kwargs_To_Self in base_classes(Trace_Call__Handler) - assert list_set(self.handler.__locals__()) == list_set(self.handler.__default_kwargs__()) + ['trace_title'] - assert self.handler.trace_title == DEFAULT_ROOT_NODE_NODE_TITLE + assert list_set(self.handler.__locals__()) == list_set(self.handler.__default_kwargs__()) + assert self.handler.config.title == DEFAULT_ROOT_NODE_NODE_TITLE assert self.handler.stack.size() == 0 assert self.handler.stack.top() is None @@ -245,8 +245,8 @@ def test_trace_calls(self): assert trace_calls(frame=None , event=None , arg='aa') == trace_calls assert len(stack) == 1 # confirm no changes made by call above - assert self.handler.stats == Trace_Call__Stats(event_call= 1, event_return=1, event_unknown=3) - self.handler.stats.event_unknown = 0 # remove these since they were caused by the bad calls above + assert self.handler.stats == Trace_Call__Stats(calls= 1, calls_skipped=1,returns=1, unknowns=3) + self.handler.stats.unknowns = 0 # remove these since they were caused by the bad calls above # case 2: with valid frame and event buy with no capture assert trace_calls(frame_1, 'call', None) == trace_calls assert len(stack) == 1 @@ -254,7 +254,7 @@ def test_trace_calls(self): # case 3: with valid frame and event buy with no capture assert stack[0].data() == Trace_Call__Stack_Node(name=DEFAULT_ROOT_NODE_NODE_TITLE).data() root_node = stack[0] - assert self.handler.stats == Trace_Call__Stats(event_call= 2, event_return=1) + assert self.handler.stats == Trace_Call__Stats(calls= 2, calls_skipped=2, returns=1) config.trace_capture_all = True assert trace_calls(frame_1, 'call', None) == trace_calls @@ -328,7 +328,7 @@ def test_trace_calls(self): frame = frame_3 , module = 'test_Trace_Call__Handler' , name = 'test_Trace_Call__Handler.test_Trace_Call__Handler.test_trace_calls' ) - assert self.handler.stats == Trace_Call__Stats(event_call=6, event_return=2) + assert self.handler.stats == Trace_Call__Stats(calls=6, calls_skipped=2, returns=2) assert trace_calls(frame_3, 'return', None) == trace_calls @@ -340,7 +340,7 @@ def test_trace_calls(self): assert node_1_a.children == [node_2] assert node_2.children == [node_3] assert node_3.children == [] - assert self.handler.stats == Trace_Call__Stats(event_call=6, event_return=3) + assert self.handler.stats == Trace_Call__Stats(calls=6, calls_skipped=2, returns=3) assert trace_calls(frame_2, 'return', None) == trace_calls assert len(stack) == 2 @@ -361,7 +361,7 @@ def test_trace_calls(self): assert node_1_a.children == [node_2] assert node_2.children == [node_3] assert node_3.children == [] - assert self.handler.stats == Trace_Call__Stats(event_call=6, event_return=5) + assert self.handler.stats == Trace_Call__Stats(calls=6, calls_skipped=2, returns=5) diff --git a/tests/utils/trace/test_Trace_Call__Print_Lines.py b/tests/utils/trace/test_Trace_Call__Print_Lines.py index 2a49482b..d3343d0d 100644 --- a/tests/utils/trace/test_Trace_Call__Print_Lines.py +++ b/tests/utils/trace/test_Trace_Call__Print_Lines.py @@ -27,7 +27,7 @@ def an_method(): folder_exists(current_temp_folder()) an_method() - return self.trace_call.create_view_model() + return self.trace_call.view_data() def test_print_lines(self): view_model = self.create_view_model() @@ -38,23 +38,26 @@ def test_print_lines(self): with patch('builtins.print') as builtins_print: print_lines.print_lines() - assert builtins_print.call_args_list == [call('┌─────┬──────────────────────────────────────────────────────────────┬───────────────────────────────────────────────┬──────┬──────┬───────┐ '), - call('│ # │ Source code │ Method Class and Name │ Self object │ Line │ Depth │ '), - call('├─────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────────┼──────┼──────┼───────┤ '), - call('│ \x1b[90m1\x1b[0m │ \x1b[1m\x1b[92m def an_method():\x1b[0m │ test_Trace_Call__Print_Lines.an_method │ │ 25 │ 1 │'), - call('│ \x1b[90m2\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m temp_folder = current_temp_folder()\x1b[0m │ test_Trace_Call__Print_Lines.an_method │ │ 26 │ 1 │'), - call('│ \x1b[90m3\x1b[0m │ \x1b[1m\x1b[92m def temp_folder_current():\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 387 │ 2 │'), - call('│ \x1b[90m4\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m return tempfile.gettempdir()\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 388 │ 2 │'), - call('│ \x1b[90m5\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m folder_exists(current_temp_folder())\x1b[0m │ test_Trace_Call__Print_Lines.an_method │ │ 27 │ 1 │'), - call('│ \x1b[90m6\x1b[0m │ \x1b[1m\x1b[92m def temp_folder_current():\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 387 │ 2 │'), - call('│ \x1b[90m7\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m return tempfile.gettempdir()\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 388 │ 2 │'), - call('│ \x1b[90m8\x1b[0m │ \x1b[1m\x1b[92m def folder_exists(path):\x1b[0m │ osbot_utils.utils.Files.folder_exists │ │ 166 │ 2 │'), - call('│ \x1b[90m9\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m return is_folder(path)\x1b[0m │ osbot_utils.utils.Files.folder_exists │ │ 167 │ 2 │'), - call('│ \x1b[90m10\x1b[0m │ \x1b[1m\x1b[92m def is_folder(target):\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 264 │ 3 │'), - call('│ \x1b[90m11\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m if isinstance(target, Path):\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 265 │ 3 │'), - call('│ \x1b[90m12\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m if type(target) is str:\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 267 │ 3 │'), - call('│ \x1b[90m13\x1b[0m │ \x1b[1m\x1b[38;2;120;120;120m return os.path.isdir(target)\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 268 │ 3 │'), - call('└─────┴──────────────────────────────────────────────────────────────┴───────────────────────────────────────────────┴──────┴──────┴───────┘')] + assert builtins_print.call_args_list == [call('--------- CALL TRACER (Lines)----------'), + call('Here are the 13 lines captured\n'), + call('┌─────┬──────┬──────────────────────────────────────────────────────────────┬───────────────────────────────────────────────┬──────┬───────┐ '), + call('│ # │ Line │ Source code │ Method Class and Name │ Self object │ Depth │ '), + call('├─────┼──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────────┼──────┼───────┤ '), + call('│ \x1b[90m1\x1b[0m │ 25 │ \x1b[1m\x1b[92m def an_method():\x1b[0m │ test_Trace_Call__Print_Lines.an_method │ │ 1 │'), + call('│ \x1b[90m2\x1b[0m │ 26 │ \x1b[1m\x1b[38;2;120;120;120m temp_folder = current_temp_folder()\x1b[0m │ test_Trace_Call__Print_Lines.an_method │ │ 1 │'), + call('│ \x1b[90m3\x1b[0m │ 387 │ \x1b[1m\x1b[92m def temp_folder_current():\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 2 │'), + call('│ \x1b[90m4\x1b[0m │ 388 │ \x1b[1m\x1b[38;2;120;120;120m return tempfile.gettempdir()\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 2 │'), + call('│ \x1b[90m5\x1b[0m │ 27 │ \x1b[1m\x1b[38;2;120;120;120m folder_exists(current_temp_folder())\x1b[0m │ test_Trace_Call__Print_Lines.an_method │ │ 1 │'), + call('│ \x1b[90m6\x1b[0m │ 387 │ \x1b[1m\x1b[92m def temp_folder_current():\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 2 │'), + call('│ \x1b[90m7\x1b[0m │ 388 │ \x1b[1m\x1b[38;2;120;120;120m return tempfile.gettempdir()\x1b[0m │ osbot_utils.utils.Files.temp_folder_current │ │ 2 │'), + call('│ \x1b[90m8\x1b[0m │ 166 │ \x1b[1m\x1b[92m def folder_exists(path):\x1b[0m │ osbot_utils.utils.Files.folder_exists │ │ 2 │'), + call('│ \x1b[90m9\x1b[0m │ 167 │ \x1b[1m\x1b[38;2;120;120;120m return is_folder(path)\x1b[0m │ osbot_utils.utils.Files.folder_exists │ │ 2 │'), + call('│ \x1b[90m10\x1b[0m │ 264 │ \x1b[1m\x1b[92m def is_folder(target):\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 3 │'), + call('│ \x1b[90m11\x1b[0m │ 265 │ \x1b[1m\x1b[38;2;120;120;120m if isinstance(target, Path):\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 3 │'), + call('│ \x1b[90m12\x1b[0m │ 267 │ \x1b[1m\x1b[38;2;120;120;120m if type(target) is str:\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 3 │'), + call('│ \x1b[90m13\x1b[0m │ 268 │ \x1b[1m\x1b[38;2;120;120;120m return os.path.isdir(target)\x1b[0m │ osbot_utils.utils.Files.is_folder │ │ 3 │'), + call('└─────┴──────┴──────────────────────────────────────────────────────────────┴───────────────────────────────────────────────┴──────┴───────┘')] + #print_lines.print_lines() diff --git a/tests/utils/trace/test_Trace_Call__Print_Traces.py b/tests/utils/trace/test_Trace_Call__Print_Traces.py index 6f6efd82..8250b617 100644 --- a/tests/utils/trace/test_Trace_Call__Print_Traces.py +++ b/tests/utils/trace/test_Trace_Call__Print_Traces.py @@ -68,7 +68,7 @@ def test_print_traces(self): with trace_call: another_function() - view_model = trace_call.create_view_model() + view_model = trace_call.view_data() trace_capture_source_code = trace_call.trace_call_handler.config.trace_capture_source_code trace_call.trace_call_print_traces.config.show_parent_info = True with patch('builtins.print') as mock_print: @@ -100,7 +100,7 @@ def test_print_traces(self): another_function() - view_model = trace_call.create_view_model() + view_model = trace_call.view_data() trace_capture_source_code = trace_call.trace_call_handler.config.trace_capture_source_code with patch('builtins.print') as mock_print: @@ -132,7 +132,7 @@ def test_print_traces(self): with patch('builtins.print') as mock_print: with trace_call: another_function() - view_model = trace_call.create_view_model() + view_model = trace_call.view_data() trace_capture_source_code = trace_call.trace_call_handler.config.trace_capture_source_code trace_call.trace_call_print_traces.print_traces(view_model, trace_capture_source_code) assert mock_print.call_args_list == [call(), @@ -164,18 +164,6 @@ def even_more_slower(): a_bit_slower() even_more_slower() - expected_stats = { 'event_call' : 21 , - 'event_exception': 0 , - 'event_line' : 72 , - 'event_return' : 18 , - 'event_unknown' : 0 } - if in_github_action(): - expected_stats['event_line'] = 73 - if 'PYCHARM_RUN_COVERAGE' in os.environ: - expected_stats['event_line'] = 73 - - - assert trace_call.stats().stats() == expected_stats config.print_duration = False config.with_duration_bigger_than = 10 / 1000 with patch('builtins.print') as mock_print: diff --git a/tests/utils/trace/test_Trace_Call__View_Model.py b/tests/utils/trace/test_Trace_Call__View_Model.py index 0c401b68..68faaff8 100644 --- a/tests/utils/trace/test_Trace_Call__View_Model.py +++ b/tests/utils/trace/test_Trace_Call__View_Model.py @@ -30,7 +30,7 @@ def test_create(self): view_model = trace_call_view_model.view_model assert len(view_model) == 4, "Four function calls should be traced" - assert view_model[0]['method_name'] == handler.trace_title , "First function in view_model should be 'traces'" + assert view_model[0]['method_name'] == handler.config.title , "First function in view_model should be 'traces'" assert view_model[1]['method_name'] == 'dummy_function' , "2nd function in view_model should be 'dummy_function'" assert view_model[2]['method_name'] == 'another_function' , "3rd function in view_model should be 'another_function'" assert view_model[3]['method_name'] == 'dummy_function' , "4th function in view_model should be 'dummy_function'" @@ -49,7 +49,7 @@ def test_create__via_Trace_Call_with(self, builtins_print): trace_call_view_model = trace_call.trace_call_view_model view_model = trace_call_view_model.view_model assert len(view_model) == 4, "Four function calls should be traced" - assert view_model[0]['method_name'] == handler.trace_title , "First function in view_model should be 'traces'" + assert view_model[0]['method_name'] == handler.config.title , "First function in view_model should be 'traces'" assert view_model[1]['method_name'] == 'dummy_function' , "2nd function in view_model should be 'dummy_function'" assert view_model[2]['method_name'] == 'another_function' , "3rd function in view_model should be 'another_function'" assert view_model[3]['method_name'] == 'dummy_function' , "4th function in view_model should be 'dummy_function'"