From dea3117ae8da529aee2a30e5f1ebd587e37596e0 Mon Sep 17 00:00:00 2001 From: Dinis Cruz Date: Wed, 10 Jan 2024 21:55:25 +0000 Subject: [PATCH] Added feature to Kwargs_To_Self to lock the attributes (good to detect unpexected changes to the self object) on Traces_Call: - added enabled config setting - added option to do a deepcopy of the locals (important when wanting to understand better the value of the locals at a particular execution point) - added multiple helpers to config to set the options: all, capture, locals, lines - removed trace_title with config.title (which is the correct place to get that value) on Trace_Call__Print_lines changed the order of item in table to have the line number showing first (since it an be quite helpful) removed some stats method that were not very stable in CI Pipeline --- osbot_utils/base_classes/Kwargs_To_Self.py | 18 +++++ osbot_utils/utils/trace/Trace_Call.py | 40 +++++++----- osbot_utils/utils/trace/Trace_Call__Config.py | 38 ++++++++++- .../utils/trace/Trace_Call__Handler.py | 49 +++++++++----- .../utils/trace/Trace_Call__Print_Lines.py | 65 ++++++++++--------- osbot_utils/utils/trace/Trace_Call__Stack.py | 9 ++- .../utils/trace/Trace_Call__Stack_Node.py | 6 ++ osbot_utils/utils/trace/Trace_Call__Stats.py | 13 ++-- tests/utils/trace/test_Trace_Call.py | 49 +------------- tests/utils/trace/test_Trace_Call_Stats.py | 11 ++-- tests/utils/trace/test_Trace_Call__Config.py | 3 +- tests/utils/trace/test_Trace_Call__Handler.py | 16 ++--- .../trace/test_Trace_Call__Print_Lines.py | 39 ++++++----- .../trace/test_Trace_Call__Print_Traces.py | 18 +---- .../trace/test_Trace_Call__View_Model.py | 4 +- 15 files changed, 209 insertions(+), 169 deletions(-) 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'"