Skip to content

Commit

Permalink
improved test coverage for test_Trace_Call__Handler
Browse files Browse the repository at this point in the history
changed methods from Call_Stack to be all stack
  • Loading branch information
DinisCruz committed Jan 6, 2024
1 parent bc705b2 commit c6a0f05
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 58 deletions.
24 changes: 11 additions & 13 deletions osbot_utils/utils/Call_Stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,19 @@

from osbot_utils.utils.Objects import obj_data

def call_stack_current_frame():
return sys._getframe()

class Call_Stack:
def call_stack_format_stack(depth=None):
return traceback.format_stack(limit=depth)

def current_frame(self):
return sys._getframe()
def call_stack_frames(depth=None):
return traceback.extract_stack(limit=depth)

def format_stack(self, depth=None):
return traceback.format_stack(limit=depth)
def call_stack_frames_data(depth=None):
frames_data = []
for frame in call_stack_frames(depth=depth):
frames_data.append(obj_data(frame))
return frames_data

def frames(self, depth=None):
return traceback.extract_stack(limit=depth)

def frames_data(self, depth=None):
frames_data = []
for frame in self.frames(depth=depth):
frames_data.append(obj_data(frame))
return frames_data

41 changes: 22 additions & 19 deletions osbot_utils/utils/trace/Trace_Call__Handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,46 @@
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
from osbot_utils.utils.trace.Trace_Call__Config import Trace_Call__Config

DEFAULT_ROOT_NODE_NODE_TITLE = 'Trace Session'

class Trace_Call__Handler(Kwargs_To_Self):
call_index : int
#title : str
stack : list
config : Trace_Call__Config


def __init__(self, **kwargs):
super().__init__(**kwargs)
self.trace_title = self.config.title or 'Trace Session' # Title for the trace
self.stack = [{"name" : self.trace_title ,
"children" : [] ,
"call_index" : self.call_index }] # Call stack information
#self.trace_capture_start_with = self.trace_capture_start_with or []
#self.trace_ignore_start_with = self.trace_ignore_start_with or []
self.trace_title = self.config.title or DEFAULT_ROOT_NODE_NODE_TITLE # Title for the trace
self.stack = [self.new_stack_node(self.trace_title, self.call_index)]


def new_stack_node(self, title, call_index):
return { "name" : title ,
"children" : [] ,
"call_index" : call_index }

def add_trace_ignore(self, value):
self.config.trace_ignore_start_with.append(value)
return

def should_capture(self, module, func_name):
capture = False
if self.config.trace_capture_all:
capture = True
else:
for item in self.config.trace_capture_start_with: # Check if the module should be captured
if module and func_name:
if self.config.trace_capture_all:
capture = True
else:
for item in self.config.trace_capture_start_with: # Check if the module should be captured
if module.startswith(item):
capture = True
break
if self.config.trace_ignore_internals and func_name.startswith('_'): # Skip private functions
capture = False

for item in self.config.trace_ignore_start_with: # Check if the module should be ignored
if module.startswith(item):
capture = True
capture = False
break
if self.config.trace_ignore_internals and func_name.startswith('_'): # Skip private functions
capture = False

for item in self.config.trace_ignore_start_with: # Check if the module should be ignored
if module.startswith(item):
capture = False
break
return capture

def map_source_code(self, frame):
Expand Down
27 changes: 13 additions & 14 deletions tests/utils/test_Call_Stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,36 @@
import traceback
from unittest import TestCase

from osbot_utils.utils.Call_Stack import Call_Stack
from osbot_utils.utils.Call_Stack import call_stack_current_frame, call_stack_format_stack, call_stack_frames, \
call_stack_frames_data
from osbot_utils.utils.Dev import pprint
from osbot_utils.utils.Misc import list_set
from osbot_utils.utils.Objects import obj_info, obj_data


class test_Call_Stack(TestCase):

def setUp(self):
self.call_stack = Call_Stack()

def test_current_frame(self):
frame = self.call_stack.current_frame()
def test_call_stack_current_frame(self):
frame = call_stack_current_frame()

assert list_set(obj_data(frame)) == ['clear', 'f_back', 'f_builtins', 'f_code',
'f_globals', 'f_lasti', 'f_lineno', 'f_locals',
'f_trace', 'f_trace_lines', 'f_trace_opcodes']

def test_format_stack(self):
formated_stack = self.call_stack.format_stack(depth=2)
def test_call_stack_format_stack(self):
formated_stack = call_stack_format_stack(depth=2)
assert len(formated_stack) ==2
for item in formated_stack:
assert type(item) is str

def test_frames(self):
assert len(self.call_stack.frames( )) > 20
assert len(self.call_stack.frames(depth=1)) == 1
assert len(self.call_stack.frames(depth=2)) == 2
assert len(self.call_stack.frames(depth=5)) == 5
def test_call_stack_frames(self):
assert len(call_stack_frames( )) > 20
assert len(call_stack_frames(depth=1)) == 1
assert len(call_stack_frames(depth=2)) == 2
assert len(call_stack_frames(depth=5)) == 5

def test_frames_data(self):
frames_data = self.call_stack.frames_data(depth=4)
def test_call_stack_frames_data(self):
frames_data = call_stack_frames_data(depth=4)
for frame_data in frames_data:
assert list_set(frame_data) == ['filename', 'line', 'lineno', 'locals', 'name']
160 changes: 148 additions & 12 deletions tests/utils/trace/test_Trace_Call__Handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest import TestCase
from unittest.mock import patch, call

from osbot_utils.utils.Call_Stack import call_stack_current_frame
from osbot_utils.utils.Dev import pprint
from osbot_utils.utils.Functions import method_line_number
from osbot_utils.utils.Misc import random_value, list_set
Expand All @@ -10,7 +11,7 @@

from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
from osbot_utils.utils.trace.Trace_Call__Config import Trace_Call__Config
from osbot_utils.utils.trace.Trace_Call__Handler import Trace_Call__Handler
from osbot_utils.utils.trace.Trace_Call__Handler import Trace_Call__Handler, DEFAULT_ROOT_NODE_NODE_TITLE


class test_Trace_Call__Handler(TestCase):
Expand All @@ -29,8 +30,147 @@ 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 self.handler.stack == [{'call_index': 0, 'children': [], 'name': DEFAULT_ROOT_NODE_NODE_TITLE}]


def test_add_trace_ignore(self):
value = random_value()
assert self.handler.config.trace_ignore_start_with == []
assert self.handler.add_trace_ignore(value) is None
assert self.handler.config.trace_ignore_start_with == [value]

# def test_map_full_name(self):
# map_source_code = self.handler.map_full_name
# config = self.handler.config

def test_map_source_code(self):
sample_frame = call_stack_current_frame().f_back
map_source_code = self.handler.map_source_code
config = self.handler.config

# case 1: with trace_capture_source_code set to False
assert config.trace_capture_source_code is False
assert map_source_code(sample_frame) == {'source_code': '', 'source_code_caller': '', 'source_code_location': ''}

# case 2: with trace_capture_source_code set to True
config.trace_capture_source_code = True
method_in_frame = test_Trace_Call__Handler.test_map_source_code
source_code_file = __file__
source_code_line_number = method_line_number(method_in_frame) + 15
source_code_location = f'{source_code_file}:{source_code_line_number}'
source_code = map_source_code(sample_frame)
assert source_code == { 'source_code': 'source_code = map_source_code(sample_frame)' ,
'source_code_caller': 'method()' ,
'source_code_location': source_code_location }



def test_new_stack_node(self):
title = random_value()
call_index = random_value()
assert self.handler.new_stack_node(title, call_index) == {'call_index': call_index, 'children': [], 'name': title}


def test_should_capture(self):
should_capture = self.handler.should_capture
config = self.handler.config

# check default config values that impact logic

assert config.trace_capture_all is False
assert config.trace_ignore_internals is True

# case 1: with invalid values on module and/or func_name
assert should_capture(module=None , func_name=None) is False
assert should_capture(module='aa' , func_name=None) is False
assert should_capture(module=None , func_name='bb') is False
assert should_capture(module='' , func_name='' ) is False
assert should_capture(module='aa' , func_name='' ) is False
assert should_capture(module='' , func_name='bb') is False

# case 2: trace_capture_all is True
config.trace_capture_all = True
assert should_capture(module='aa' ,func_name='bb') is True
assert should_capture(module=None, func_name='bb') is False
assert should_capture(module='aa', func_name=None) is False

# case 3: with trace_capture_start_with set
config.trace_capture_all = False
config.trace_capture_start_with = ['module_a']
assert should_capture(module='module_a' ,func_name='func_a' ) is True
assert should_capture(module='module_a' ,func_name='func_b' ) is True
assert should_capture(module='module_a', func_name='_internal') is False
assert should_capture(module='module_b', func_name='func_a' ) is False
assert should_capture(module='module_b', func_name='______' ) is False

# case 4: with trace_ignore_internals set for False
config.trace_ignore_internals = False
assert should_capture(module='module_a', func_name='func_a' ) is True
assert should_capture(module='module_a', func_name='_internal') is True

# case 5: with trace_ignore_start_with set
config.trace_ignore_internals = True
config.trace_ignore_start_with = ['module_a']
config.trace_capture_start_with = ['module_b']
assert should_capture(module='module_a' ,func_name='func_a' ) is False
assert should_capture(module='module_a' ,func_name='func_b' ) is False
assert should_capture(module='module_a', func_name='_internal') is False
assert should_capture(module='module_b', func_name='func_a' ) is True
assert should_capture(module='module_b', func_name='______' ) is False

# case 6: Mixed Cases for trace_capture_start_with logic
config.trace_capture_start_with = ['mod']
assert should_capture(module='modXYZ', func_name='func_a') is True

# case 7 Edge Cases in Configurations
config.trace_capture_start_with = ['']
assert should_capture(module='anything', func_name='func_a') is True # Assuming empty string matches any module

config.trace_capture_start_with = ['mod']
config.trace_ignore_start_with = ['mod']
assert should_capture(module='modXYZ', func_name='func_a') is False # Overlapping configurations

# case 8: Boundary Values for startswith Logic
config.trace_ignore_start_with = []
config.trace_capture_start_with = ['mod']
assert should_capture(module='mod', func_name='func_a') is True

# case 9: nteraction Between trace_ignore_internals and trace_capture_start_with/trace_ignore_start_with
config.trace_capture_start_with = ['mod']
config.trace_ignore_internals = True
assert should_capture(module='mod', func_name='_internalFunc') is False

# case 10: Functionality When All Configs are Empty or Default
config.trace_capture_all = False
config.trace_capture_start_with = []
config.trace_ignore_start_with = []
config.trace_ignore_internals = True
assert should_capture(module='anyModule', func_name='anyFunc') is False # Assuming default behavior is to not capture

# Case 11: Overlapping Patterns
config.trace_capture_start_with = ['common']
config.trace_ignore_start_with = ['common']
assert should_capture(module='commonModule', func_name='func') is False

# Case 12: Whitespace and Special Characters in Names
assert should_capture(module=' ', func_name='func') is False
assert should_capture(module='module', func_name='@#$') is False

# Case 13: Long String Names
long_string = 'a' * 1000
assert should_capture(module=long_string, func_name=long_string) is False

# Case 14: Special Characters in Start With Lists
config.trace_capture_start_with = ['^', '$', '*']
config.trace_ignore_start_with = ['?', '+', '|']
assert should_capture(module='^module', func_name='func') is True
assert should_capture(module='$module', func_name='func') is True
assert should_capture(module='*module', func_name='func') is True
assert should_capture(module='?module', func_name='func') is False
assert should_capture(module='+module', func_name='func') is False
assert should_capture(module='|module', func_name='func') is False


def test_trace_calls__direct_invoke(self):
frame = sys._getframe() # get a valid frame object
Expand All @@ -44,9 +184,9 @@ def test_trace_calls__direct_invoke(self):
stack_1 = self.handler.stack[1]


assert stack_0 == dict(call_index = 0 ,
children = [stack_1] ,
name = 'Trace Session' )
assert stack_0 == dict(call_index = 0 ,
children = [stack_1] ,
name = DEFAULT_ROOT_NODE_NODE_TITLE)


stack_1_locals = dict(stack_1.get('locals'))
Expand All @@ -68,25 +208,21 @@ def test_trace_calls__direct_invoke(self):


def test_trace_calls__direct_invoke__variations(self):
sample_frame = call_stack_current_frame().f_back
self.handler.config.trace_capture_start_with = ['test']
self.handler.config.trace_capture_source_code = True
self.handler.trace_calls( sys._getframe(), 'call', None)
self.handler.trace_calls(sample_frame, 'call', None)

method_in_frame = test_Trace_Call__Handler.test_trace_calls__direct_invoke__variations
source_code_file = __file__
source_code_line_number = method_line_number(method_in_frame) + 3
source_code_line_number = method_line_number(method_in_frame) + 4
source_code_location = f'{source_code_file}:{source_code_line_number}'

stack_1 = self.handler.stack[1]
assert stack_1.get('name' ) == 'test_Trace_Call__Handler.test_Trace_Call__Handler.test_trace_calls__direct_invoke__variations'
assert stack_1.get('source_code' ) == "self.handler.trace_calls( sys._getframe(), 'call', None)"
assert stack_1.get('source_code' ) == "self.handler.trace_calls(sample_frame, 'call', None)"
assert stack_1.get('source_code_caller' ) == 'method()'
assert stack_1.get('source_code_location') == source_code_location
assert len(self.handler.stack) == 2


def test_add_trace_ignore(self):
value = random_value()
assert self.handler.config.trace_ignore_start_with == []
assert self.handler.add_trace_ignore(value) is None
assert self.handler.config.trace_ignore_start_with == [value]

0 comments on commit c6a0f05

Please sign in to comment.