diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..e5a3a61 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,9 @@ +[run] +source = objwatch +omit = + objwatch/wrappers/* + +[report] +show_missing = True +skip_covered = False +fail_under = 0 diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 409d2ca..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep diff --git a/tests/test_base.py b/tests/test_base.py deleted file mode 100644 index 35d9483..0000000 --- a/tests/test_base.py +++ /dev/null @@ -1,505 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep - -import os -import runpy -import importlib -import unittest -from unittest.mock import MagicMock, patch -import logging -from io import StringIO -import objwatch -from objwatch.config import ObjWatchConfig -from objwatch.wrappers import BaseWrapper, TensorShapeWrapper, ABCWrapper -from objwatch.core import ObjWatch -from objwatch.targets import Targets -from objwatch.tracer import Tracer -from tests.util import strip_line_numbers - -try: - import torch -except ImportError: - torch = None - - -golden_log = """DEBUG:objwatch: run __main__. -DEBUG:objwatch: | run __main__.TestClass -DEBUG:objwatch: | end __main__.TestClass -DEBUG:objwatch: | run __main__.main -DEBUG:objwatch: | | run __main__.TestClass.method -DEBUG:objwatch: | | | upd TestClass.attr None -> 1 -DEBUG:objwatch: | | end __main__.TestClass.method -DEBUG:objwatch: | end __main__.main -DEBUG:objwatch: end __main__.""" - - -class TestTracer(unittest.TestCase): - def setUp(self): - self.test_script = 'tests/test_script.py' - with open(self.test_script, 'w') as f: - f.write( - """ -class TestClass: - def method(self): - self.attr = 1 - self.attr += 1 - -def main(): - obj = TestClass() - obj.method() - -if __name__ == '__main__': - main() -""" - ) - - def tearDown(self): - os.remove(self.test_script) - - @patch('objwatch.utils.logger.get_logger') - def test_tracer(self, mock_logger): - mock_logger.return_value = unittest.mock.Mock() - obj_watch = ObjWatch([self.test_script]) - obj_watch.start() - - with self.assertLogs('objwatch', level='DEBUG') as log: - runpy.run_path(self.test_script, run_name="__main__") - - obj_watch.stop() - - test_log = '\n'.join(log.output) - self.assertIn(golden_log, strip_line_numbers(test_log)) - - -class TestWatch(unittest.TestCase): - def setUp(self): - self.test_script = 'tests/test_script.py' - with open(self.test_script, 'w') as f: - f.write( - """ -class TestClass: - def method(self): - self.attr = 1 - self.attr += 1 - -def main(): - obj = TestClass() - obj.method() - -if __name__ == '__main__': - main() -""" - ) - - def tearDown(self): - os.remove(self.test_script) - - @patch('objwatch.utils.logger.get_logger') - def test_tracer(self, mock_logger): - mock_logger.return_value = unittest.mock.Mock() - obj_watch = objwatch.watch([self.test_script], simple=True) - - with self.assertLogs('objwatch', level='DEBUG') as log: - runpy.run_path(self.test_script, run_name="__main__") - - obj_watch.stop() - - test_log = '\n'.join(log.output) - self.assertIn(golden_log, strip_line_numbers(test_log)) - - -class TestBaseWrapper(unittest.TestCase): - def setUp(self): - self.base_logger = BaseWrapper() - - def test_wrap_call_with_simple_args(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1', 'arg2') - mock_frame.f_code.co_argcount = 2 - mock_frame.f_locals = {'arg1': 10, 'arg2': [1, 2, 3, 4, 5]} - expected_call_msg = "'0':10, '1':(list)[1, 2, 3, '... (2 more elements)']" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_return_with_simple_return(self): - result = 20 - expected_return_msg = "20" - actual_return_msg = self.base_logger.wrap_return('test_func', result) - self.assertEqual(actual_return_msg, expected_return_msg) - - def test_wrap_call_with_no_args(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = () - mock_frame.f_code.co_argcount = 0 - mock_frame.f_locals = {} - expected_call_msg = "" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_return_with_list(self): - result = [True, False, True, False] - expected_return_msg = "[(list)[True, False, True, '... (1 more elements)']]" - actual_return_msg = self.base_logger.wrap_return('test_func', result) - self.assertEqual(actual_return_msg, expected_return_msg) - - def test_wrap_call_with_dict_under_limit(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg1': {'a': 1, 'b': 2}} - expected_call_msg = "'0':(dict)[('a', 1), ('b', 2)]" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_dict_over_limit(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg1': {'a': 1, 'b': 2, 'c': 3, 'd': 4}} - expected_call_msg = "'0':(dict)[('a', 1), ('b', 2), ('c', 3), '... (1 more elements)']" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_set_under_limit(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg1': {1, 2}} - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertTrue(actual_call_msg.startswith("'0':(set)[")) - self.assertIn("1", actual_call_msg) - self.assertIn("2", actual_call_msg) - self.assertFalse("..." in actual_call_msg, "Should not contain '...' for set under limit") - - def test_wrap_call_with_set_over_limit(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg1': {1, 2, 3, 4, 5}} - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertTrue(actual_call_msg.startswith("'0':(set)[")) - self.assertIn("... (2 more elements)", actual_call_msg) - - def test_wrap_call_with_empty_list(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg1': []} - expected_call_msg = "'0':(list)[]" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_empty_set(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg1': set()} - expected_call_msg = "'0':(set)[]" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_empty_dict(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg1': {}} - expected_call_msg = "'0':(dict)[]" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_dict_non_element_keys(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg1',) - mock_frame.f_code.co_argcount = 1 - - class CustomObj: - pass - - test_dict = {CustomObj(): 123, 'a': 456} - mock_frame.f_locals = {'arg1': test_dict} - - expected_call_msg = "'0':(dict)[2 elements]" - actual_call_msg = self.base_logger.wrap_call('test_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - -@unittest.skipIf(torch is None, "PyTorch not installed, skipping TensorShapeWrapper tests.") -class TestTensorShapeWrapper(unittest.TestCase): - def setUp(self): - self.tensor_shape_logger = TensorShapeWrapper() - - def test_wrap_call_with_tensor(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('tensor_arg',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'tensor_arg': torch.randn(3, 4)} - tensor_shape = mock_frame.f_locals['tensor_arg'].shape - expected_call_msg = f"'0':{tensor_shape}" - actual_call_msg = self.tensor_shape_logger.wrap_call('test_tensor_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_return_with_tensor(self): - tensor = torch.randn(5, 6) - expected_return_msg = f"{tensor.shape}" - actual_return_msg = self.tensor_shape_logger.wrap_return('test_tensor_func', tensor) - self.assertEqual(actual_return_msg, expected_return_msg) - - def test_wrap_call_with_mixed_args(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('tensor_arg', 'value') - mock_frame.f_code.co_argcount = 2 - mock_frame.f_locals = {'tensor_arg': torch.randn(2, 2), 'value': 42} - tensor_shape = mock_frame.f_locals['tensor_arg'].shape - expected_call_msg = f"'0':{tensor_shape}, '1':42" - actual_call_msg = self.tensor_shape_logger.wrap_call('test_mixed_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_tensor_list_over_limit(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg_tensors',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg_tensors': [torch.randn(2, 2) for _ in range(5)]} - expected_call_msg = ( - "'0':(list)[torch.Size([2, 2]), torch.Size([2, 2]), torch.Size([2, 2]), '... (2 more elements)']" - ) - actual_call_msg = self.tensor_shape_logger.wrap_call('test_tensor_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_tensor_set_over_limit(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg_tensors',) - mock_frame.f_code.co_argcount = 1 - - tensors_set = {torch.randn(2, 2) for _ in range(5)} - mock_frame.f_locals = {'arg_tensors': tensors_set} - expected_call_msg = ( - "'0':(set)[torch.Size([2, 2]), torch.Size([2, 2]), torch.Size([2, 2]), '... (2 more elements)']" - ) - actual_call_msg = self.tensor_shape_logger.wrap_call('test_tensor_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_tensor_dict_over_limit(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg_tensors',) - mock_frame.f_code.co_argcount = 1 - - tensors_dict = {f"key_{i}": torch.randn(2, 2) for i in range(5)} - mock_frame.f_locals = {'arg_tensors': tensors_dict} - expected_call_msg = "'0':(dict)[('key_0', torch.Size([2, 2])), ('key_1', torch.Size([2, 2])), ('key_2', torch.Size([2, 2])), '... (2 more elements)']" - actual_call_msg = self.tensor_shape_logger.wrap_call('test_tensor_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - def test_wrap_call_with_empty_tensor_list(self): - mock_frame = MagicMock() - mock_frame.f_code.co_varnames = ('arg_tensors',) - mock_frame.f_code.co_argcount = 1 - mock_frame.f_locals = {'arg_tensors': []} - expected_call_msg = "'0':(list)[]" - actual_call_msg = self.tensor_shape_logger.wrap_call('test_tensor_func', mock_frame) - self.assertEqual(actual_call_msg, expected_call_msg) - - -class TestCustomWrapper(unittest.TestCase): - def setUp(self): - class CustomWrapper(ABCWrapper): - def wrap_call(self, func_name, frame): - return f"CustomCall: {func_name} called with args {frame.f_locals}" - - def wrap_return(self, func_name, result): - return f"CustomReturn: {func_name} returned {result}" - - def wrap_upd(self, old_value, current_value): - old_msg = self._format_value(old_value) - current_msg = self._format_value(current_value) - return old_msg, current_msg - - self.custom_wrapper = CustomWrapper - - self.log_stream = StringIO() - self.logger = logging.getLogger('objwatch') - self.logger.setLevel(logging.DEBUG) - handler = logging.StreamHandler(self.log_stream) - formatter = logging.Formatter('%(message)s') - handler.setFormatter(formatter) - self.logger.addHandler(handler) - self.logger.propagate = False - - self.obj_watch = ObjWatch( - targets=['example_module.py'], wrapper=self.custom_wrapper, output=None, level=logging.DEBUG, simple=True - ) - self.obj_watch.start() - - def test_custom_wrapper_call_and_return(self): - mock_frame = MagicMock() - mock_frame.f_code.co_filename = 'example_module.py' - mock_frame.f_code.co_name = 'custom_func' - mock_frame.f_locals = {'arg1': 'value1'} - mock_frame.f_lineno = 42 - - trace_func = self.obj_watch.tracer.trace_factory() - - trace_func(mock_frame, 'call', None) - - trace_func(mock_frame, 'return', 'custom_result') - - self.obj_watch.stop() - - self.log_stream.seek(0) - logs = self.log_stream.read() - - self.assertIn(".custom_func <- CustomCall: custom_func called with args {'arg1': 'value1'}", logs) - self.assertIn(".custom_func -> CustomReturn: custom_func returned custom_result", logs) - - def tearDown(self): - self.obj_watch.stop() - handlers = self.logger.handlers[:] - for handler in handlers: - handler.close() - self.logger.removeHandler(handler) - - -class TestUnsupportWrapper(unittest.TestCase): - - def setUp(self): - - class UnsupportWrapper: - def wrap_call(self, func_name, frame): - return f"CustomCall: {func_name} called with args {frame.f_locals}" - - def wrap_return(self, func_name, result): - return f"CustomReturn: {func_name} returned {result}" - - def wrap_upd(self, old_value, current_value): - old_msg = self._format_value(old_value) - current_msg = self._format_value(current_value) - return old_msg, current_msg - - self.unsupport_wrapper = UnsupportWrapper - - def test_unsupport_wrapper(self): - with self.assertRaises(ValueError): - ObjWatch( - targets=['example_module.py'], - wrapper=self.unsupport_wrapper, - output=None, - level=logging.DEBUG, - simple=True, - ) - - -class TestTargetsStr(unittest.TestCase): - def test_targets_with_submodules(self): - processed = Targets(['importlib']).get_targets() - self.assertIn('importlib', processed) - module_info = processed['importlib'] - - expected_functions = ["import_module", "reload", "invalidate_caches"] - for func in expected_functions: - self.assertIn(func, module_info.get('functions', [])) - - expected_globals = [ - "_pack_uint32", - "__all__", - "_RELOADING", - "_unpack_uint32", - ] - for global_var in expected_globals: - self.assertIn(global_var, module_info.get('globals', [])) - - self.assertEqual(len(module_info.get('classes', {})), 0) - - -class TestTargetsModule(unittest.TestCase): - def test_targets_with_submodules(self): - processed = Targets([importlib]).get_targets() - - self.assertIn('importlib', processed) - module_info = processed['importlib'] - - expected_functions = ["import_module", "reload", "invalidate_caches"] - for func in expected_functions: - self.assertIn(func, module_info.get('functions', [])) - - expected_globals = [ - "_pack_uint32", - "__all__", - "_RELOADING", - "_unpack_uint32", - ] - for global_var in expected_globals: - self.assertIn(global_var, module_info.get('globals', [])) - - self.assertEqual(len(module_info.get('classes', {})), 0) - - -class TestLoggerForce(unittest.TestCase): - def setUp(self): - import objwatch.utils.logger - - objwatch.utils.logger.FORCE = False - - def tearDown(self): - import objwatch.utils.logger - - objwatch.utils.logger.FORCE = False - - @patch('builtins.print') - def test_log_info_force_true(self, mock_print): - import objwatch.utils.logger - - objwatch.utils.logger.create_logger(level='force') - - msg = "Forced log message" - objwatch.utils.logger.log_info(msg) - - mock_print.assert_called_with(msg, flush=True) - - @patch('builtins.print') - def test_log_debug_force_true(self, mock_print): - import objwatch.utils.logger - - objwatch.utils.logger.create_logger(level='force') - - msg = "Forced debug message" - objwatch.utils.logger.log_debug(msg) - - mock_print.assert_called_with(msg, flush=True) - - @patch('builtins.print') - def test_log_warn_force_true(self, mock_print): - import objwatch.utils.logger - - objwatch.utils.logger.create_logger(level='force') - - msg = "Forced warning message" - objwatch.utils.logger.log_warn(msg) - - mock_print.assert_called_with(msg, flush=True) - - @patch('objwatch.utils.logger.logger.info') - @patch('objwatch.utils.logger.logger.debug') - @patch('objwatch.utils.logger.logger.warning') - @patch('builtins.print') - def test_log_functions_force_false(self, mock_print, mock_warning, mock_debug, mock_info): - import objwatch.utils.logger - - objwatch.utils.logger.create_logger(level=logging.DEBUG) - - info_msg = "Normal log message" - objwatch.utils.logger.log_info(info_msg) - mock_info.assert_called_with(info_msg) - mock_print.assert_not_called() - - debug_msg = "Normal debug message" - objwatch.utils.logger.log_debug(debug_msg) - mock_debug.assert_called_with(debug_msg) - mock_print.assert_not_called() - - warn_msg = "Normal warning message" - objwatch.utils.logger.log_warn(warn_msg) - mock_warning.assert_called_with(warn_msg) - mock_print.assert_not_called() - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_comprehensive_exclude.py b/tests/test_comprehensive_exclude.py deleted file mode 100644 index adc853c..0000000 --- a/tests/test_comprehensive_exclude.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive test for exclude functionality in track_all mode. -""" - -import sys -import os - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from objwatch.tracer import Tracer -from objwatch.config import ObjWatchConfig - -# Import test module from the same directory -from .utils.example_module import TestClass - - -def test_comprehensive_exclude(): - """Test comprehensive exclude functionality with track_all mode.""" - print("Testing comprehensive exclude functionality...") - - # Test 1: Basic method and attribute exclusion - config = ObjWatchConfig( - targets=["tests.utils.example_module:TestClass"], - exclude_targets=[ - "tests.utils.example_module:TestClass.excluded_method()", - "tests.utils.example_module:TestClass.excluded_attr", - ], - with_locals=False, - ) - - tracer = Tracer(config) - - # Test method tracking - assert tracer._should_trace_method( - 'tests.utils.example_module', 'TestClass', 'tracked_method' - ), "tracked_method should be tracked" - assert not tracer._should_trace_method( - 'tests.utils.example_module', 'TestClass', 'excluded_method' - ), "excluded_method should be excluded" - - # Test attribute tracking - assert tracer._should_trace_attribute( - 'tests.utils.example_module', 'TestClass', 'tracked_attr' - ), "tracked_attr should be tracked" - assert not tracer._should_trace_attribute( - 'tests.utils.example_module', 'TestClass', 'excluded_attr' - ), "excluded_attr should be excluded" - - # Test 2: Multiple exclusions - config2 = ObjWatchConfig( - targets=["tests.utils.example_module:TestClass"], - exclude_targets=[ - "tests.utils.example_module:TestClass.excluded_method()", - "tests.utils.example_module:TestClass.excluded_attr", - "tests.utils.example_module:TestClass.tracked_method()", # Exclude a method that would normally be tracked - ], - with_locals=False, - ) - - tracer2 = Tracer(config2) - - assert not tracer2._should_trace_method( - 'tests.utils.example_module', 'TestClass', 'tracked_method' - ), "tracked_method should be excluded when explicitly excluded" - assert not tracer2._should_trace_method( - 'tests.utils.example_module', 'TestClass', 'excluded_method' - ), "excluded_method should be excluded" - assert tracer2._should_trace_attribute( - 'tests.utils.example_module', 'TestClass', 'tracked_attr' - ), "tracked_attr should still be tracked" - - # Test 3: No exclusions (everything should be tracked) - config3 = ObjWatchConfig(targets=["tests.utils.example_module:TestClass"], exclude_targets=[], with_locals=False) - - tracer3 = Tracer(config3) - - assert tracer3._should_trace_method( - 'tests.utils.example_module', 'TestClass', 'tracked_method' - ), "tracked_method should be tracked with no exclusions" - assert tracer3._should_trace_method( - 'tests.utils.example_module', 'TestClass', 'excluded_method' - ), "excluded_method should be tracked with no exclusions" - assert tracer3._should_trace_attribute( - 'tests.utils.example_module', 'TestClass', 'tracked_attr' - ), "tracked_attr should be tracked" - assert tracer3._should_trace_attribute( - 'tests.utils.example_module', 'TestClass', 'excluded_attr' - ), "excluded_attr should be tracked with no exclusions" - - -if __name__ == "__main__": - test_comprehensive_exclude() diff --git a/tests/test_coverup_1.py b/tests/test_coverup_1.py new file mode 100644 index 0000000..aa5f788 --- /dev/null +++ b/tests/test_coverup_1.py @@ -0,0 +1,83 @@ +# file: objwatch/utils/weak.py:81-97 +# asked: {"lines": [81, 86, 87, 88, 89, 90, 91, 92, 94, 95, 96, 97], "branches": [[88, 89]]} +# gained: {"lines": [81, 86, 87, 88, 89, 90, 91, 92, 94, 95, 96, 97], "branches": [[88, 89]]} + +import pytest +from weakref import ref +from objwatch.utils.weak import WeakIdKeyDictionary + + +class TestWeakIdKeyDictionaryCommitRemovals: + + def test_commit_removals_empty_pending(self): + """Test _commit_removals when _pending_removals is empty.""" + weak_dict = WeakIdKeyDictionary() + # Initially empty, should return immediately + weak_dict._commit_removals() + assert len(weak_dict._pending_removals) == 0 + assert len(weak_dict.data) == 0 + + def test_commit_removals_with_valid_keys(self): + """Test _commit_removals with keys that exist in data.""" + obj1 = object() + obj2 = object() + weak_dict = WeakIdKeyDictionary() + + # Add objects to data + weak_dict.data[obj1] = "value1" + weak_dict.data[obj2] = "value2" + + # Add pending removals + weak_dict._pending_removals = [obj1, obj2] + + # Commit removals + weak_dict._commit_removals() + + # Verify removals were processed + assert len(weak_dict._pending_removals) == 0 + assert len(weak_dict.data) == 0 + assert obj1 not in weak_dict.data + assert obj2 not in weak_dict.data + + def test_commit_removals_with_missing_keys(self): + """Test _commit_removals with keys that don't exist in data (KeyError case).""" + obj1 = object() + obj2 = object() + weak_dict = WeakIdKeyDictionary() + + # Add only obj1 to data, but both to pending removals + weak_dict.data[obj1] = "value1" + weak_dict._pending_removals = [obj1, obj2] + + # Commit removals - should handle KeyError for obj2 + weak_dict._commit_removals() + + # Verify removals were processed + assert len(weak_dict._pending_removals) == 0 + assert len(weak_dict.data) == 0 + assert obj1 not in weak_dict.data + assert obj2 not in weak_dict.data + + def test_commit_removals_mixed_scenario(self): + """Test _commit_removals with mix of existing and non-existing keys.""" + obj1 = object() + obj2 = object() + obj3 = object() + weak_dict = WeakIdKeyDictionary() + + # Add some objects to data + weak_dict.data[obj1] = "value1" + weak_dict.data[obj3] = "value3" + + # Add pending removals including non-existent key + weak_dict._pending_removals = [obj1, obj2, obj3] + + # Commit removals + weak_dict._commit_removals() + + # Verify all removals processed + assert len(weak_dict._pending_removals) == 0 + assert len(weak_dict.data) == 0 + assert obj1 not in weak_dict.data + assert obj2 not in weak_dict.data + assert obj3 not in weak_dict.data diff --git a/tests/test_coverup_10.py b/tests/test_coverup_10.py new file mode 100644 index 0000000..64c0d8f --- /dev/null +++ b/tests/test_coverup_10.py @@ -0,0 +1,75 @@ +# file: objwatch/tracer.py:340-359 +# asked: {"lines": [340, 341, 351, 352, 354, 355, 357, 358], "branches": [[351, 352], [351, 354], [354, 355], [354, 357]]} +# gained: {"lines": [340, 341, 351, 352, 354, 355, 357, 358], "branches": [[351, 352], [351, 354], [354, 355], [354, 357]]} + +import pytest +from unittest.mock import Mock, patch +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerShouldTraceGlobal: + """Test cases for Tracer._should_trace_global method to achieve full coverage.""" + + def test_should_trace_global_with_globals_disabled(self): + """Test that _should_trace_global returns False when with_globals is False.""" + config = ObjWatchConfig(targets=["test_module"], with_globals=False) + tracer = Tracer(config) + + result = tracer._should_trace_global("test_module", "test_global") + assert result is False + + def test_should_trace_global_with_empty_global_index(self): + """Test that _should_trace_global returns True for non-builtin globals when global_index is empty.""" + config = ObjWatchConfig(targets=["test_module"], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {} + tracer.builtin_fields = {"__builtins__", "self", "__name__"} + + # Test with non-builtin global + result = tracer._should_trace_global("test_module", "custom_global") + assert result is True + + # Test with builtin global + result = tracer._should_trace_global("test_module", "__builtins__") + assert result is False + + def test_should_trace_global_with_global_index(self): + """Test that _should_trace_global correctly checks global_index and exclude_global_index.""" + config = ObjWatchConfig(targets=["test_module"], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {"test_module": {"global1", "global2", "global3"}} + tracer.exclude_global_index = {"test_module": {"global2"}} + tracer.builtin_fields = set() + + # Test global in global_index and not in exclude_global_index + result = tracer._should_trace_global("test_module", "global1") + assert result is True + + # Test global in global_index but also in exclude_global_index + result = tracer._should_trace_global("test_module", "global2") + assert result is False + + # Test global not in global_index + result = tracer._should_trace_global("test_module", "unknown_global") + assert result is False + + # Test global in different module + result = tracer._should_trace_global("other_module", "global1") + assert result is False + + def test_should_trace_global_with_module_not_in_indices(self): + """Test that _should_trace_global handles modules not present in indices.""" + config = ObjWatchConfig(targets=["test_module"], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {"existing_module": {"some_global"}} + tracer.exclude_global_index = {"existing_module": set()} + tracer.builtin_fields = set() + + # Test module not in global_index + result = tracer._should_trace_global("non_existent_module", "any_global") + assert result is False + + # Test module not in exclude_global_index + result = tracer._should_trace_global("non_existent_module", "any_global") + assert result is False diff --git a/tests/test_coverup_100.py b/tests/test_coverup_100.py new file mode 100644 index 0000000..a5ad03f --- /dev/null +++ b/tests/test_coverup_100.py @@ -0,0 +1,108 @@ +# file: objwatch/wrappers/cpu_memory_wrapper.py:112-123 +# asked: {"lines": [112, 123], "branches": []} +# gained: {"lines": [112, 123], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.wrappers.cpu_memory_wrapper import CPUMemoryWrapper + + +class TestCPUMemoryWrapperWrapUpd: + """Test cases for CPUMemoryWrapper.wrap_upd method.""" + + def test_wrap_upd_returns_empty_old_and_formatted_memory(self, monkeypatch): + """Test that wrap_upd returns empty string for old value and formatted memory for new value.""" + # Setup + wrapper = CPUMemoryWrapper() + old_value = "old_value" + current_value = "current_value" + + # Mock the memory capture and formatting + mock_memory_stats = {'total': 8589934592, 'available': 4294967296, 'percent': 50.0} + mock_formatted = "total: 8589934592 | available: 4294967296 | percent: 50.0" + + # Patch the internal methods + monkeypatch.setattr(wrapper, '_capture_memory', Mock(return_value=mock_memory_stats)) + monkeypatch.setattr(wrapper, '_format_memory', Mock(return_value=mock_formatted)) + + # Execute + result = wrapper.wrap_upd(old_value, current_value) + + # Assert + assert result == ("", mock_formatted) + wrapper._capture_memory.assert_called_once() + wrapper._format_memory.assert_called_once_with(mock_memory_stats) + + def test_wrap_upd_with_different_memory_types(self, monkeypatch): + """Test wrap_upd with different memory type configurations.""" + # Setup - modify mem_types to test different configurations + original_mem_types = CPUMemoryWrapper.mem_types.copy() + CPUMemoryWrapper.mem_types = ['used', 'free'] + + try: + wrapper = CPUMemoryWrapper() + old_value = 42 + current_value = 100 + + # Mock memory stats for different configuration + mock_memory_stats = {'used': 4294967296, 'free': 4294967296} + mock_formatted = "used: 4294967296 | free: 4294967296" + + # Patch the internal methods + monkeypatch.setattr(wrapper, '_capture_memory', Mock(return_value=mock_memory_stats)) + monkeypatch.setattr(wrapper, '_format_memory', Mock(return_value=mock_formatted)) + + # Execute + result = wrapper.wrap_upd(old_value, current_value) + + # Assert + assert result == ("", mock_formatted) + wrapper._capture_memory.assert_called_once() + wrapper._format_memory.assert_called_once_with(mock_memory_stats) + finally: + # Cleanup - restore original mem_types + CPUMemoryWrapper.mem_types = original_mem_types + + def test_wrap_upd_with_none_values(self, monkeypatch): + """Test wrap_upd with None values for old and current values.""" + # Setup + wrapper = CPUMemoryWrapper() + + # Mock memory capture and formatting + mock_memory_stats = {'total': 8589934592, 'available': 4294967296, 'percent': 50.0} + mock_formatted = "total: 8589934592 | available: 4294967296 | percent: 50.0" + + # Patch the internal methods + monkeypatch.setattr(wrapper, '_capture_memory', Mock(return_value=mock_memory_stats)) + monkeypatch.setattr(wrapper, '_format_memory', Mock(return_value=mock_formatted)) + + # Execute + result = wrapper.wrap_upd(None, None) + + # Assert + assert result == ("", mock_formatted) + wrapper._capture_memory.assert_called_once() + wrapper._format_memory.assert_called_once_with(mock_memory_stats) + + def test_wrap_upd_with_complex_objects(self, monkeypatch): + """Test wrap_upd with complex objects as values.""" + # Setup + wrapper = CPUMemoryWrapper() + old_value = {"key": "old_value", "nested": {"data": [1, 2, 3]}} + current_value = {"key": "new_value", "nested": {"data": [4, 5, 6]}} + + # Mock memory capture and formatting + mock_memory_stats = {'total': 8589934592, 'available': 4294967296, 'percent': 50.0} + mock_formatted = "total: 8589934592 | available: 4294967296 | percent: 50.0" + + # Patch the internal methods + monkeypatch.setattr(wrapper, '_capture_memory', Mock(return_value=mock_memory_stats)) + monkeypatch.setattr(wrapper, '_format_memory', Mock(return_value=mock_formatted)) + + # Execute + result = wrapper.wrap_upd(old_value, current_value) + + # Assert + assert result == ("", mock_formatted) + wrapper._capture_memory.assert_called_once() + wrapper._format_memory.assert_called_once_with(mock_memory_stats) diff --git a/tests/test_coverup_101.py b/tests/test_coverup_101.py new file mode 100644 index 0000000..33004eb --- /dev/null +++ b/tests/test_coverup_101.py @@ -0,0 +1,46 @@ +# file: objwatch/utils/weak.py:146-147 +# asked: {"lines": [146, 147], "branches": []} +# gained: {"lines": [146, 147], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryGet: + def test_get_existing_key_returns_value(self): + """Test that get() returns the correct value for an existing key.""" + + # Use a class instance instead of object() to allow weak references + class TestClass: + pass + + obj = TestClass() + d = WeakIdKeyDictionary() + d[obj] = "test_value" + + result = d.get(obj) + assert result == "test_value" + + def test_get_nonexistent_key_returns_default(self): + """Test that get() returns the default value for a nonexistent key.""" + + class TestClass: + pass + + obj = TestClass() + d = WeakIdKeyDictionary() + + result = d.get(obj, "default_value") + assert result == "default_value" + + def test_get_nonexistent_key_returns_none_without_default(self): + """Test that get() returns None for a nonexistent key when no default is provided.""" + + class TestClass: + pass + + obj = TestClass() + d = WeakIdKeyDictionary() + + result = d.get(obj) + assert result is None diff --git a/tests/test_coverup_102.py b/tests/test_coverup_102.py new file mode 100644 index 0000000..44442ec --- /dev/null +++ b/tests/test_coverup_102.py @@ -0,0 +1,76 @@ +# file: objwatch/utils/weak.py:108-109 +# asked: {"lines": [108, 109], "branches": []} +# gained: {"lines": [108, 109], "branches": []} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryGetItem: + def test_getitem_with_existing_key(self): + """Test __getitem__ with an existing key to cover lines 108-109""" + + # Create a dictionary and add a key-value pair using a class instance + # that can be weakly referenced + class TestObject: + def __init__(self, value): + self.value = value + + obj = TestObject("test") + weak_dict = WeakIdKeyDictionary() + weak_dict[obj] = "test_value" + + # Test that we can retrieve the value using the same object + result = weak_dict[obj] + assert result == "test_value" + + def test_getitem_with_nonexistent_key(self): + """Test __getitem__ with a non-existent key to ensure KeyError is raised""" + + # Use a class instance that can be weakly referenced + class TestObject: + pass + + obj = TestObject() + weak_dict = WeakIdKeyDictionary() + + # Test that accessing a non-existent key raises KeyError + with pytest.raises(KeyError): + _ = weak_dict[obj] + + def test_getitem_with_different_ref_type(self, monkeypatch): + """Test __getitem__ with a custom ref_type to ensure ref_type is used""" + # Create a custom ref_type that tracks calls + call_count = 0 + + class TrackingRefType: + def __init__(self, key, callback=None): + nonlocal call_count + call_count += 1 + self.key = key + self.callback = callback + + def __hash__(self): + return hash(self.key) + + def __eq__(self, other): + return isinstance(other, TrackingRefType) and self.key == other.key + + # Create dictionary with custom ref_type + weak_dict = WeakIdKeyDictionary(ref_type=TrackingRefType) + + # Use a class instance that can be weakly referenced + class TestObject: + pass + + obj = TestObject() + weak_dict[obj] = "test_value" + + # Reset call count and test __getitem__ + call_count = 0 + result = weak_dict[obj] + + # Verify ref_type was called and value was retrieved + assert call_count == 1 + assert result == "test_value" diff --git a/tests/test_coverup_103.py b/tests/test_coverup_103.py new file mode 100644 index 0000000..c77dccd --- /dev/null +++ b/tests/test_coverup_103.py @@ -0,0 +1,74 @@ +# file: objwatch/utils/weak.py:215-217 +# asked: {"lines": [215, 216, 217], "branches": []} +# gained: {"lines": [215, 216, 217], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary + + +class TestWeakIdKeyDictionaryIor: + def test_ior_with_dict(self): + """Test __ior__ method with a dictionary.""" + + # Create objects that can be weakly referenced + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + + other_dict = {obj2: "value2"} + result = weak_dict.__ior__(other_dict) + + assert result is weak_dict + assert weak_dict[obj1] == "value1" + assert weak_dict[obj2] == "value2" + + def test_ior_with_empty_dict(self): + """Test __ior__ method with an empty dictionary.""" + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + + empty_dict = {} + result = weak_dict.__ior__(empty_dict) + + assert result is weak_dict + assert weak_dict[obj1] == "value1" + assert len(weak_dict) == 1 + + def test_ior_with_weak_dict(self): + """Test __ior__ method with another WeakIdKeyDictionary.""" + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + obj3 = TestObject("obj3") + + weak_dict1 = WeakIdKeyDictionary() + weak_dict2 = WeakIdKeyDictionary() + + weak_dict1[obj1] = "value1" + weak_dict2[obj2] = "value2" + weak_dict2[obj3] = "value3" + + result = weak_dict1.__ior__(weak_dict2) + + assert result is weak_dict1 + assert weak_dict1[obj1] == "value1" + assert weak_dict1[obj2] == "value2" + assert weak_dict1[obj3] == "value3" + assert len(weak_dict1) == 3 diff --git a/tests/test_coverup_104.py b/tests/test_coverup_104.py new file mode 100644 index 0000000..2e9168d --- /dev/null +++ b/tests/test_coverup_104.py @@ -0,0 +1,95 @@ +# file: objwatch/utils/weak.py:202-203 +# asked: {"lines": [202, 203], "branches": []} +# gained: {"lines": [202, 203], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionarySetdefault: + + def test_setdefault_existing_key(self): + """Test setdefault when key already exists - returns existing value""" + d = WeakIdKeyDictionary() + + # Use a class instance that supports weak references + class TestClass: + def __init__(self, value): + self.value = value + + key = TestClass("test") + d[key] = "existing_value" + + result = d.setdefault(key, "default_value") + + assert result == "existing_value" + assert d[key] == "existing_value" + + def test_setdefault_new_key_with_default(self): + """Test setdefault when key doesn't exist with default value""" + d = WeakIdKeyDictionary() + + # Use a class instance that supports weak references + class TestClass: + pass + + key = TestClass() + + result = d.setdefault(key, "default_value") + + assert result == "default_value" + assert d[key] == "default_value" + + def test_setdefault_new_key_no_default(self): + """Test setdefault when key doesn't exist without default value""" + d = WeakIdKeyDictionary() + + # Use a class instance that supports weak references + class TestClass: + pass + + key = TestClass() + + result = d.setdefault(key) + + assert result is None + assert key in d + assert d[key] is None + + def test_setdefault_new_key_none_default(self): + """Test setdefault when key doesn't exist with explicit None default""" + d = WeakIdKeyDictionary() + + # Use a class instance that supports weak references + class TestClass: + pass + + key = TestClass() + + result = d.setdefault(key, None) + + assert result is None + assert d[key] is None + + def test_setdefault_with_custom_ref_type(self): + """Test setdefault with custom ref_type""" + + class CustomRef(WeakIdRef): + pass + + d = WeakIdKeyDictionary(ref_type=CustomRef) + + # Use a class instance that supports weak references + class TestClass: + pass + + key = TestClass() + + result = d.setdefault(key, "custom_value") + + assert result == "custom_value" + assert d[key] == "custom_value" + + # Verify the key is stored using CustomRef + for ref_key in d.data.keys(): + assert isinstance(ref_key, CustomRef) diff --git a/tests/test_coverup_106.py b/tests/test_coverup_106.py new file mode 100644 index 0000000..d1903a7 --- /dev/null +++ b/tests/test_coverup_106.py @@ -0,0 +1,177 @@ +# file: objwatch/wrappers/cpu_memory_wrapper.py:64-72 +# asked: {"lines": [64, 71, 72], "branches": []} +# gained: {"lines": [64, 71, 72], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.wrappers.cpu_memory_wrapper import CPUMemoryWrapper + + +class TestCPUMemoryWrapper: + def test_capture_memory_default_mem_types(self): + """Test _capture_memory with default mem_types configuration.""" + wrapper = CPUMemoryWrapper() + + # Mock psutil.virtual_memory to return a predictable result + mock_virtual_memory = Mock() + mock_virtual_memory._asdict.return_value = { + 'total': 8589934592, # 8GB + 'available': 4294967296, # 4GB + 'percent': 50.0, + 'used': 4294967296, + 'free': 4294967296, + 'active': 3221225472, + 'inactive': 1073741824, + 'buffers': 268435456, + 'cached': 1073741824, + 'shared': 134217728, + 'slab': 67108864, + 'wired': 1610612736, + } + + with patch('objwatch.wrappers.cpu_memory_wrapper.psutil.virtual_memory', return_value=mock_virtual_memory): + result = wrapper._capture_memory() + + # Should only include the default mem_types + expected_keys = {'total', 'available', 'percent'} + assert set(result.keys()) == expected_keys + assert result['total'] == 8589934592 + assert result['available'] == 4294967296 + assert result['percent'] == 50.0 + + def test_capture_memory_custom_mem_types(self): + """Test _capture_memory with custom mem_types configuration.""" + # Set custom mem_types before creating wrapper + CPUMemoryWrapper.mem_types = ['used', 'free', 'buffers'] + wrapper = CPUMemoryWrapper() + + # Mock psutil.virtual_memory to return a predictable result + mock_virtual_memory = Mock() + mock_virtual_memory._asdict.return_value = { + 'total': 8589934592, + 'available': 4294967296, + 'percent': 50.0, + 'used': 4294967296, + 'free': 4294967296, + 'active': 3221225472, + 'inactive': 1073741824, + 'buffers': 268435456, + 'cached': 1073741824, + 'shared': 134217728, + 'slab': 67108864, + 'wired': 1610612736, + } + + with patch('objwatch.wrappers.cpu_memory_wrapper.psutil.virtual_memory', return_value=mock_virtual_memory): + result = wrapper._capture_memory() + + # Should only include the custom mem_types + expected_keys = {'used', 'free', 'buffers'} + assert set(result.keys()) == expected_keys + assert result['used'] == 4294967296 + assert result['free'] == 4294967296 + assert result['buffers'] == 268435456 + + def test_capture_memory_missing_mem_type(self): + """Test _capture_memory when a requested mem_type is not in psutil result.""" + # Set mem_types to include a non-existent field + CPUMemoryWrapper.mem_types = ['total', 'available', 'nonexistent_field'] + wrapper = CPUMemoryWrapper() + + # Mock psutil.virtual_memory to return a result without the nonexistent field + mock_virtual_memory = Mock() + mock_virtual_memory._asdict.return_value = { + 'total': 8589934592, + 'available': 4294967296, + 'percent': 50.0, + 'used': 4294967296, + 'free': 4294967296, + } + + with patch('objwatch.wrappers.cpu_memory_wrapper.psutil.virtual_memory', return_value=mock_virtual_memory): + with pytest.raises(KeyError, match='nonexistent_field'): + wrapper._capture_memory() + + def test_capture_memory_empty_mem_types(self): + """Test _capture_memory with empty mem_types configuration.""" + # Set empty mem_types + CPUMemoryWrapper.mem_types = [] + wrapper = CPUMemoryWrapper() + + # Mock psutil.virtual_memory + mock_virtual_memory = Mock() + mock_virtual_memory._asdict.return_value = {'total': 8589934592, 'available': 4294967296, 'percent': 50.0} + + with patch('objwatch.wrappers.cpu_memory_wrapper.psutil.virtual_memory', return_value=mock_virtual_memory): + result = wrapper._capture_memory() + + # Should return empty dict when no mem_types are configured + assert result == {} + + def test_capture_memory_all_available_fields(self): + """Test _capture_memory when requesting all available memory fields.""" + # Set mem_types to include all possible fields + CPUMemoryWrapper.mem_types = [ + 'total', + 'available', + 'percent', + 'used', + 'free', + 'active', + 'inactive', + 'buffers', + 'cached', + 'shared', + 'slab', + 'wired', + ] + wrapper = CPUMemoryWrapper() + + # Mock psutil.virtual_memory to return all fields + mock_virtual_memory = Mock() + mock_virtual_memory._asdict.return_value = { + 'total': 8589934592, + 'available': 4294967296, + 'percent': 50.0, + 'used': 4294967296, + 'free': 4294967296, + 'active': 3221225472, + 'inactive': 1073741824, + 'buffers': 268435456, + 'cached': 1073741824, + 'shared': 134217728, + 'slab': 67108864, + 'wired': 1610612736, + } + + with patch('objwatch.wrappers.cpu_memory_wrapper.psutil.virtual_memory', return_value=mock_virtual_memory): + result = wrapper._capture_memory() + + # Should include all requested fields that exist in the psutil result + expected_keys = { + 'total', + 'available', + 'percent', + 'used', + 'free', + 'active', + 'inactive', + 'buffers', + 'cached', + 'shared', + 'slab', + 'wired', + } + assert set(result.keys()) == expected_keys + assert result['total'] == 8589934592 + assert result['available'] == 4294967296 + assert result['percent'] == 50.0 + assert result['used'] == 4294967296 + assert result['free'] == 4294967296 + assert result['active'] == 3221225472 + assert result['inactive'] == 1073741824 + assert result['buffers'] == 268435456 + assert result['cached'] == 1073741824 + assert result['shared'] == 134217728 + assert result['slab'] == 67108864 + assert result['wired'] == 1610612736 diff --git a/tests/test_coverup_107.py b/tests/test_coverup_107.py new file mode 100644 index 0000000..a52afa9 --- /dev/null +++ b/tests/test_coverup_107.py @@ -0,0 +1,95 @@ +# file: objwatch/utils/weak.py:178-188 +# asked: {"lines": [178, 188], "branches": []} +# gained: {"lines": [178, 188], "branches": []} + +import pytest +from weakref import ref +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryKeyrefs: + def test_keyrefs_empty(self): + """Test keyrefs returns empty list when dictionary is empty.""" + weak_dict = WeakIdKeyDictionary() + result = weak_dict.keyrefs() + assert result == [] + assert isinstance(result, list) + + def test_keyrefs_with_items(self): + """Test keyrefs returns list of weak references when dictionary has items.""" + + # Use objects that can be weakly referenced + class KeyObject: + def __init__(self, name): + self.name = name + + key1 = KeyObject("key1") + key2 = KeyObject("key2") + value1 = "value1" + value2 = "value2" + + weak_dict = WeakIdKeyDictionary() + weak_dict[key1] = value1 + weak_dict[key2] = value2 + + result = weak_dict.keyrefs() + assert isinstance(result, list) + assert len(result) == 2 + + # Verify the references are the same as stored in self.data + assert result == list(weak_dict.data) + + # Verify the references are WeakIdRef instances + for ref_obj in result: + assert isinstance(ref_obj, WeakIdRef) + + def test_keyrefs_after_removal(self): + """Test keyrefs returns updated list after item removal.""" + + class KeyObject: + def __init__(self, name): + self.name = name + + key1 = KeyObject("key1") + key2 = KeyObject("key2") + value1 = "value1" + value2 = "value2" + + weak_dict = WeakIdKeyDictionary() + weak_dict[key1] = value1 + weak_dict[key2] = value2 + + # Get initial keyrefs + initial_refs = weak_dict.keyrefs() + assert len(initial_refs) == 2 + + # Remove one item + del weak_dict[key1] + + # Get updated keyrefs + updated_refs = weak_dict.keyrefs() + assert len(updated_refs) == 1 + assert updated_refs == list(weak_dict.data) + + def test_keyrefs_during_iteration(self): + """Test keyrefs works correctly during iteration.""" + + class KeyObject: + def __init__(self, name): + self.name = name + + key1 = KeyObject("key1") + key2 = KeyObject("key2") + value1 = "value1" + value2 = "value2" + + weak_dict = WeakIdKeyDictionary() + weak_dict[key1] = value1 + weak_dict[key2] = value2 + + # Use keyrefs during iteration over the dictionary + for key_ref in weak_dict.keyrefs(): + # The references might not be live, so we need to check + key = key_ref() if hasattr(key_ref, '__call__') else key_ref + if key is not None: + assert key in weak_dict diff --git a/tests/test_coverup_108.py b/tests/test_coverup_108.py new file mode 100644 index 0000000..fe5ad16 --- /dev/null +++ b/tests/test_coverup_108.py @@ -0,0 +1,176 @@ +# file: objwatch/tracer.py:442-460 +# asked: {"lines": [442, 449, 450, 452, 453, 454, 455, 456, 457, 458, 459, 460], "branches": [[449, 0], [449, 450], [452, 0], [452, 453], [454, 455], [454, 456], [456, 457], [456, 458], [458, 0], [458, 459], [459, 458], [459, 460]]} +# gained: {"lines": [442, 449, 450, 452, 453, 454, 455, 456, 457, 458, 459, 460], "branches": [[449, 0], [449, 450], [452, 0], [452, 453], [454, 455], [454, 456], [456, 457], [456, 458], [458, 0], [458, 459], [459, 458], [459, 460]]} + +import pytest +from unittest.mock import Mock +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestObject: + def __init__(self): + self.list_attr = [1, 2, 3] + self.dict_attr = {'a': 1, 'b': 2} + self.tuple_attr = (1, 2, 3) + self.set_attr = {1, 2, 3} + self.non_sequence_attr = "string" + self.callable_attr = lambda x: x + 1 + + +class TestObjectNoDict: + """Test object without __dict__ attribute""" + + __slots__ = ['attr1', 'attr2'] + + +class TestObjectNoWeakref: + """Test object without __weakref__ in class""" + + pass + + +# Remove __weakref__ from TestObjectNoWeakref class +if hasattr(TestObjectNoWeakref, '__weakref__'): + delattr(TestObjectNoWeakref, '__weakref__') + + +def create_mock_frame(locals_dict): + """Create a mock frame object for testing""" + frame = Mock() + frame.f_locals = locals_dict + return frame + + +def test_update_objects_lens_with_sequence_types(): + """Test _update_objects_lens with object containing sequence types""" + config = ObjWatchConfig(targets=['__main__'], with_locals=True, with_globals=True) + tracer = Tracer(config) + + test_obj = TestObject() + frame = create_mock_frame({'self': test_obj}) + + # Call the method + tracer._update_objects_lens(frame) + + # Verify object is tracked + assert test_obj in tracer.tracked_objects + assert test_obj in tracer.tracked_objects_lens + + # Verify attributes are stored correctly + tracked_attrs = tracer.tracked_objects[test_obj] + assert 'list_attr' in tracked_attrs + assert 'dict_attr' in tracked_attrs + assert 'tuple_attr' in tracked_attrs + assert 'set_attr' in tracked_attrs + assert 'non_sequence_attr' in tracked_attrs + assert 'callable_attr' not in tracked_attrs # Callables should be filtered out + + # Verify sequence lengths are tracked + lens_dict = tracer.tracked_objects_lens[test_obj] + assert lens_dict['list_attr'] == 3 + assert lens_dict['dict_attr'] == 2 + assert lens_dict['tuple_attr'] == 3 + assert lens_dict['set_attr'] == 3 + assert 'non_sequence_attr' not in lens_dict # Non-sequence should not have length tracked + + +def test_update_objects_lens_no_self_in_locals(): + """Test _update_objects_lens when 'self' is not in frame locals""" + config = ObjWatchConfig(targets=['__main__'], with_locals=True, with_globals=True) + tracer = Tracer(config) + + frame = create_mock_frame({}) # No 'self' in locals + + # Call the method - should not raise any exceptions + tracer._update_objects_lens(frame) + + # Verify no objects were tracked + assert len(tracer.tracked_objects) == 0 + assert len(tracer.tracked_objects_lens) == 0 + + +def test_update_objects_lens_object_no_dict(): + """Test _update_objects_lens with object that has no __dict__ attribute""" + config = ObjWatchConfig(targets=['__main__'], with_locals=True, with_globals=True) + tracer = Tracer(config) + + test_obj = TestObjectNoDict() + test_obj.attr1 = "value1" + test_obj.attr2 = "value2" + + frame = create_mock_frame({'self': test_obj}) + + # Call the method + tracer._update_objects_lens(frame) + + # Verify object is not tracked (no __dict__) + assert test_obj not in tracer.tracked_objects + assert test_obj not in tracer.tracked_objects_lens + + +def test_update_objects_lens_object_no_weakref(): + """Test _update_objects_lens with object whose class has no __weakref__""" + config = ObjWatchConfig(targets=['__main__'], with_locals=True, with_globals=True) + tracer = Tracer(config) + + test_obj = TestObjectNoWeakref() + test_obj.some_attr = [1, 2, 3] + + frame = create_mock_frame({'self': test_obj}) + + # Call the method + tracer._update_objects_lens(frame) + + # Verify object is not tracked (no __weakref__ in class) + assert test_obj not in tracer.tracked_objects + assert test_obj not in tracer.tracked_objects_lens + + +def test_update_objects_lens_multiple_calls_same_object(): + """Test _update_objects_lens called multiple times on same object""" + config = ObjWatchConfig(targets=['__main__'], with_locals=True, with_globals=True) + tracer = Tracer(config) + + test_obj = TestObject() + frame = create_mock_frame({'self': test_obj}) + + # First call + tracer._update_objects_lens(frame) + + # Modify sequence attributes + test_obj.list_attr.append(4) + test_obj.dict_attr['c'] = 3 + + # Second call + tracer._update_objects_lens(frame) + + # Verify lengths are updated + lens_dict = tracer.tracked_objects_lens[test_obj] + assert lens_dict['list_attr'] == 4 + assert lens_dict['dict_attr'] == 3 + + +def test_update_objects_lens_empty_sequences(): + """Test _update_objects_lens with empty sequence types""" + config = ObjWatchConfig(targets=['__main__'], with_locals=True, with_globals=True) + tracer = Tracer(config) + + class EmptySequences: + def __init__(self): + self.empty_list = [] + self.empty_dict = {} + self.empty_tuple = () + self.empty_set = set() + + test_obj = EmptySequences() + frame = create_mock_frame({'self': test_obj}) + + tracer._update_objects_lens(frame) + + # Verify empty sequences are tracked with length 0 + lens_dict = tracer.tracked_objects_lens[test_obj] + assert lens_dict['empty_list'] == 0 + assert lens_dict['empty_dict'] == 0 + assert lens_dict['empty_tuple'] == 0 + assert lens_dict['empty_set'] == 0 diff --git a/tests/test_coverup_109.py b/tests/test_coverup_109.py new file mode 100644 index 0000000..706f6dd --- /dev/null +++ b/tests/test_coverup_109.py @@ -0,0 +1,85 @@ +# file: objwatch/event_handls.py:328-346 +# asked: {"lines": [328, 332, 333, 334, 335, 336, 338, 339, 342, 343, 344, 346], "branches": [[332, 0], [332, 333], [335, 336], [335, 338]]} +# gained: {"lines": [328, 332, 333, 334, 335, 336, 338, 339, 342, 343, 344, 346], "branches": [[332, 0], [332, 333], [335, 336], [335, 338]]} + +import pytest +import xml.etree.ElementTree as ET +from unittest.mock import Mock, patch, MagicMock +import sys + + +class TestEventHandlsSaveXML: + """Test cases for EventHandls.save_xml method to achieve full coverage.""" + + def test_save_xml_without_et_indent_success(self, monkeypatch): + """Test save_xml when ET.indent is not available and output_xml is set.""" + from objwatch.event_handls import EventHandls + + # Mock the logger functions to capture calls + mock_log_info = Mock() + mock_log_warn = Mock() + monkeypatch.setattr('objwatch.event_handls.log_info', mock_log_info) + monkeypatch.setattr('objwatch.event_handls.log_warn', mock_log_warn) + + # Create instance with output_xml + event_handls = EventHandls(output_xml="/tmp/test_output.xml") + event_handls.is_xml_saved = False + event_handls.stack_root = ET.Element('ObjWatch') + + # Remove ET.indent to simulate older Python versions + monkeypatch.delattr(ET, 'indent', raising=False) + + # Mock tree.write to avoid actual file I/O + with patch('xml.etree.ElementTree.ElementTree') as MockElementTree: + mock_tree_instance = Mock() + MockElementTree.return_value = mock_tree_instance + mock_tree_instance.write = Mock() + + event_handls.save_xml() + + # Verify XML was saved + assert event_handls.is_xml_saved is True + + # Verify warning was logged about missing ET.indent + mock_log_warn.assert_called_once() + warning_msg = mock_log_warn.call_args[0][0] + assert "Current Python version not support `xml.etree.ElementTree.indent`" in warning_msg + + def test_save_xml_no_output_xml(self, monkeypatch): + """Test save_xml when output_xml is None (should do nothing).""" + from objwatch.event_handls import EventHandls + + # Mock the logger functions + mock_log_info = Mock() + monkeypatch.setattr('objwatch.event_handls.log_info', mock_log_info) + + # Create instance without output_xml + event_handls = EventHandls(output_xml=None) + + # Mock the XML creation to verify it's not called + with patch('xml.etree.ElementTree.ElementTree') as mock_et: + event_handls.save_xml() + + # Verify no XML operations were performed + mock_et.assert_not_called() + mock_log_info.assert_not_called() + + def test_save_xml_already_saved(self, monkeypatch): + """Test save_xml when is_xml_saved is already True (should do nothing).""" + from objwatch.event_handls import EventHandls + + # Mock the logger functions + mock_log_info = Mock() + monkeypatch.setattr('objwatch.event_handls.log_info', mock_log_info) + + # Create instance with output_xml but already saved + event_handls = EventHandls(output_xml="/tmp/test_output.xml") + event_handls.is_xml_saved = True + + # Mock the XML creation to verify it's not called + with patch('xml.etree.ElementTree.ElementTree') as mock_et: + event_handls.save_xml() + + # Verify no XML operations were performed + mock_et.assert_not_called() + mock_log_info.assert_not_called() diff --git a/tests/test_coverup_11.py b/tests/test_coverup_11.py new file mode 100644 index 0000000..e5a66aa --- /dev/null +++ b/tests/test_coverup_11.py @@ -0,0 +1,261 @@ +# file: objwatch/core.py:106-156 +# asked: {"lines": [106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 154, 156], "branches": []} +# gained: {"lines": [106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 154, 156], "branches": []} + +import pytest +import logging +from types import ModuleType +from typing import List, Union +from unittest.mock import Mock, patch +from objwatch.core import watch +from objwatch.wrappers import ABCWrapper + + +class TestWatchFunction: + """Test cases for the watch function to achieve full coverage.""" + + def test_watch_with_all_parameters(self, monkeypatch): + """Test watch function with all parameters provided.""" + # Mock the ObjWatch class and its start method + mock_objwatch_instance = Mock() + mock_objwatch_instance.start = Mock() + + with patch('objwatch.core.ObjWatch') as mock_objwatch_class: + mock_objwatch_class.return_value = mock_objwatch_instance + + # Call watch with all parameters + targets = ['test_module.py'] + exclude_targets = ['excluded_module.py'] + framework = 'torch' + indexes = [0, 1] + output = 'test_output.log' + output_xml = 'test_output.xml' + level = logging.INFO + simple = True + wrapper = Mock(spec=ABCWrapper) + with_locals = True + with_globals = True + + result = watch( + targets=targets, + exclude_targets=exclude_targets, + framework=framework, + indexes=indexes, + output=output, + output_xml=output_xml, + level=level, + simple=simple, + wrapper=wrapper, + with_locals=with_locals, + with_globals=with_globals, + ) + + # Verify ObjWatch was instantiated with correct parameters + mock_objwatch_class.assert_called_once_with( + targets=targets, + exclude_targets=exclude_targets, + framework=framework, + indexes=indexes, + output=output, + output_xml=output_xml, + level=level, + simple=simple, + wrapper=wrapper, + with_locals=with_locals, + with_globals=with_globals, + ) + + # Verify start was called + mock_objwatch_instance.start.assert_called_once() + + # Verify the returned instance + assert result == mock_objwatch_instance + + def test_watch_with_minimal_parameters(self, monkeypatch): + """Test watch function with minimal parameters (only required ones).""" + # Mock the ObjWatch class and its start method + mock_objwatch_instance = Mock() + mock_objwatch_instance.start = Mock() + + with patch('objwatch.core.ObjWatch') as mock_objwatch_class: + mock_objwatch_class.return_value = mock_objwatch_instance + + # Call watch with only required parameters + targets = ['test_module.py'] + + result = watch(targets=targets) + + # Verify ObjWatch was instantiated with correct default parameters + mock_objwatch_class.assert_called_once_with( + targets=targets, + exclude_targets=None, + framework=None, + indexes=None, + output=None, + output_xml=None, + level=logging.DEBUG, + simple=False, + wrapper=None, + with_locals=False, + with_globals=False, + ) + + # Verify start was called + mock_objwatch_instance.start.assert_called_once() + + # Verify the returned instance + assert result == mock_objwatch_instance + + def test_watch_with_module_targets(self, monkeypatch): + """Test watch function with module type targets.""" + # Mock the ObjWatch class and its start method + mock_objwatch_instance = Mock() + mock_objwatch_instance.start = Mock() + + with patch('objwatch.core.ObjWatch') as mock_objwatch_class: + mock_objwatch_class.return_value = mock_objwatch_instance + + # Create mock modules + mock_module1 = Mock(spec=ModuleType) + mock_module1.__name__ = 'test_module1' + mock_module2 = Mock(spec=ModuleType) + mock_module2.__name__ = 'test_module2' + + # Call watch with module targets + targets = [mock_module1, mock_module2] + + result = watch(targets=targets) + + # Verify ObjWatch was instantiated with module targets + mock_objwatch_class.assert_called_once_with( + targets=targets, + exclude_targets=None, + framework=None, + indexes=None, + output=None, + output_xml=None, + level=logging.DEBUG, + simple=False, + wrapper=None, + with_locals=False, + with_globals=False, + ) + + # Verify start was called + mock_objwatch_instance.start.assert_called_once() + + # Verify the returned instance + assert result == mock_objwatch_instance + + def test_watch_with_mixed_targets(self, monkeypatch): + """Test watch function with mixed string and module targets.""" + # Mock the ObjWatch class and its start method + mock_objwatch_instance = Mock() + mock_objwatch_instance.start = Mock() + + with patch('objwatch.core.ObjWatch') as mock_objwatch_class: + mock_objwatch_class.return_value = mock_objwatch_instance + + # Create mock module + mock_module = Mock(spec=ModuleType) + mock_module.__name__ = 'test_module' + + # Call watch with mixed targets + targets = ['test_file.py', mock_module] + + result = watch(targets=targets) + + # Verify ObjWatch was instantiated with mixed targets + mock_objwatch_class.assert_called_once_with( + targets=targets, + exclude_targets=None, + framework=None, + indexes=None, + output=None, + output_xml=None, + level=logging.DEBUG, + simple=False, + wrapper=None, + with_locals=False, + with_globals=False, + ) + + # Verify start was called + mock_objwatch_instance.start.assert_called_once() + + # Verify the returned instance + assert result == mock_objwatch_instance + + def test_watch_with_exclude_targets(self, monkeypatch): + """Test watch function with exclude_targets parameter.""" + # Mock the ObjWatch class and its start method + mock_objwatch_instance = Mock() + mock_objwatch_instance.start = Mock() + + with patch('objwatch.core.ObjWatch') as mock_objwatch_class: + mock_objwatch_class.return_value = mock_objwatch_instance + + # Call watch with exclude_targets + targets = ['test_module.py'] + exclude_targets = ['excluded1.py', 'excluded2.py'] + + result = watch(targets=targets, exclude_targets=exclude_targets) + + # Verify ObjWatch was instantiated with exclude_targets + mock_objwatch_class.assert_called_once_with( + targets=targets, + exclude_targets=exclude_targets, + framework=None, + indexes=None, + output=None, + output_xml=None, + level=logging.DEBUG, + simple=False, + wrapper=None, + with_locals=False, + with_globals=False, + ) + + # Verify start was called + mock_objwatch_instance.start.assert_called_once() + + # Verify the returned instance + assert result == mock_objwatch_instance + + def test_watch_with_custom_wrapper(self, monkeypatch): + """Test watch function with custom wrapper.""" + # Mock the ObjWatch class and its start method + mock_objwatch_instance = Mock() + mock_objwatch_instance.start = Mock() + + with patch('objwatch.core.ObjWatch') as mock_objwatch_class: + mock_objwatch_class.return_value = mock_objwatch_instance + + # Create a custom wrapper + custom_wrapper = Mock(spec=ABCWrapper) + + # Call watch with custom wrapper + targets = ['test_module.py'] + + result = watch(targets=targets, wrapper=custom_wrapper) + + # Verify ObjWatch was instantiated with custom wrapper + mock_objwatch_class.assert_called_once_with( + targets=targets, + exclude_targets=None, + framework=None, + indexes=None, + output=None, + output_xml=None, + level=logging.DEBUG, + simple=False, + wrapper=custom_wrapper, + with_locals=False, + with_globals=False, + ) + + # Verify start was called + mock_objwatch_instance.start.assert_called_once() + + # Verify the returned instance + assert result == mock_objwatch_instance diff --git a/tests/test_coverup_110.py b/tests/test_coverup_110.py new file mode 100644 index 0000000..c40fa33 --- /dev/null +++ b/tests/test_coverup_110.py @@ -0,0 +1,196 @@ +# file: objwatch/tracer.py:325-338 +# asked: {"lines": [325, 326, 336, 337], "branches": []} +# gained: {"lines": [325, 326, 336, 337], "branches": []} + +import pytest +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerShouldTraceFunction: + """Test cases for Tracer._should_trace_function method.""" + + def test_should_trace_function_included_and_not_excluded(self, monkeypatch): + """Test that function is traced when included in targets and not in exclude targets.""" + + # Mock the Targets class to avoid module loading issues + def mock_get_filename_targets(self): + return set() + + def mock_get_targets(self): + return {'test_module': {'functions': ['test_func']}} + + def mock_get_exclude_targets(self): + return {} + + monkeypatch.setattr('objwatch.targets.Targets.get_filename_targets', mock_get_filename_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_targets', mock_get_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_exclude_targets', mock_get_exclude_targets) + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Clear cache to ensure fresh test + tracer._should_trace_function.cache_clear() + + result = tracer._should_trace_function('test_module', 'test_func') + assert result is True + + def test_should_trace_function_included_but_excluded(self, monkeypatch): + """Test that function is not traced when included in targets but also in exclude targets.""" + + def mock_get_filename_targets(self): + return set() + + def mock_get_targets(self): + return {'test_module': {'functions': ['test_func']}} + + def mock_get_exclude_targets(self): + return {'test_module': {'functions': ['test_func']}} + + monkeypatch.setattr('objwatch.targets.Targets.get_filename_targets', mock_get_filename_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_targets', mock_get_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_exclude_targets', mock_get_exclude_targets) + + config = ObjWatchConfig(targets=['test_module'], exclude_targets=['test_module']) + tracer = Tracer(config) + + # Clear cache to ensure fresh test + tracer._should_trace_function.cache_clear() + + result = tracer._should_trace_function('test_module', 'test_func') + assert result is False + + def test_should_trace_function_not_included(self, monkeypatch): + """Test that function is not traced when not included in targets.""" + + def mock_get_filename_targets(self): + return set() + + def mock_get_targets(self): + return {'test_module': {'functions': ['other_func']}} + + def mock_get_exclude_targets(self): + return {} + + monkeypatch.setattr('objwatch.targets.Targets.get_filename_targets', mock_get_filename_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_targets', mock_get_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_exclude_targets', mock_get_exclude_targets) + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Clear cache to ensure fresh test + tracer._should_trace_function.cache_clear() + + result = tracer._should_trace_function('test_module', 'test_func') + assert result is False + + def test_should_trace_function_module_not_in_targets(self, monkeypatch): + """Test that function is not traced when module is not in targets.""" + + def mock_get_filename_targets(self): + return set() + + def mock_get_targets(self): + return {'other_module': {'functions': ['test_func']}} + + def mock_get_exclude_targets(self): + return {} + + monkeypatch.setattr('objwatch.targets.Targets.get_filename_targets', mock_get_filename_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_targets', mock_get_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_exclude_targets', mock_get_exclude_targets) + + config = ObjWatchConfig(targets=['other_module']) + tracer = Tracer(config) + + # Clear cache to ensure fresh test + tracer._should_trace_function.cache_clear() + + result = tracer._should_trace_function('test_module', 'test_func') + assert result is False + + def test_should_trace_function_with_exclude_only(self, monkeypatch): + """Test that function is not traced when only in exclude targets.""" + + def mock_get_filename_targets(self): + return set() + + def mock_get_targets(self): + return {'test_module': {'functions': []}} # Empty functions list + + def mock_get_exclude_targets(self): + return {'test_module': {'functions': ['test_func']}} + + monkeypatch.setattr('objwatch.targets.Targets.get_filename_targets', mock_get_filename_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_targets', mock_get_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_exclude_targets', mock_get_exclude_targets) + + config = ObjWatchConfig(targets=['test_module'], exclude_targets=['test_module']) + tracer = Tracer(config) + + # Clear cache to ensure fresh test + tracer._should_trace_function.cache_clear() + + result = tracer._should_trace_function('test_module', 'test_func') + assert result is False + + def test_should_trace_function_multiple_functions(self, monkeypatch): + """Test tracing with multiple functions in same module.""" + + def mock_get_filename_targets(self): + return set() + + def mock_get_targets(self): + return {'test_module': {'functions': ['func1', 'func2', 'func3']}} + + def mock_get_exclude_targets(self): + return {'test_module': {'functions': ['func2']}} + + monkeypatch.setattr('objwatch.targets.Targets.get_filename_targets', mock_get_filename_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_targets', mock_get_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_exclude_targets', mock_get_exclude_targets) + + config = ObjWatchConfig(targets=['test_module'], exclude_targets=['test_module']) + tracer = Tracer(config) + + # Clear cache to ensure fresh test + tracer._should_trace_function.cache_clear() + + # func1 should be traced (included, not excluded) + assert tracer._should_trace_function('test_module', 'func1') is True + + # func2 should not be traced (included but excluded) + assert tracer._should_trace_function('test_module', 'func2') is False + + # func3 should be traced (included, not excluded) + assert tracer._should_trace_function('test_module', 'func3') is True + + # func4 should not be traced (not included) + assert tracer._should_trace_function('test_module', 'func4') is False + + def test_should_trace_function_empty_functions_list(self, monkeypatch): + """Test that function is not traced when functions list is empty.""" + + def mock_get_filename_targets(self): + return set() + + def mock_get_targets(self): + return {'test_module': {'functions': []}} # Empty functions list + + def mock_get_exclude_targets(self): + return {} + + monkeypatch.setattr('objwatch.targets.Targets.get_filename_targets', mock_get_filename_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_targets', mock_get_targets) + monkeypatch.setattr('objwatch.targets.Targets.get_exclude_targets', mock_get_exclude_targets) + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Clear cache to ensure fresh test + tracer._should_trace_function.cache_clear() + + result = tracer._should_trace_function('test_module', 'test_func') + assert result is False diff --git a/tests/test_coverup_112.py b/tests/test_coverup_112.py new file mode 100644 index 0000000..6228f7d --- /dev/null +++ b/tests/test_coverup_112.py @@ -0,0 +1,114 @@ +# file: objwatch/utils/weak.py:121-122 +# asked: {"lines": [121, 122], "branches": []} +# gained: {"lines": [121, 122], "branches": []} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionarySetItem: + + def test_setitem_creates_weak_ref(self): + """Test that __setitem__ creates a WeakIdRef and stores the value""" + + # Create a test object that can be weakly referenced + class TestKey: + def __init__(self, value): + self.value = value + + key_obj = TestKey("test_key") + value = "test_value" + + # Create dictionary and set item + weak_dict = WeakIdKeyDictionary() + weak_dict[key_obj] = value + + # Verify the value was stored + assert weak_dict[key_obj] == value + + # Verify a WeakIdRef was created and stored in data + assert len(weak_dict.data) == 1 + stored_ref = next(iter(weak_dict.data.keys())) + assert isinstance(stored_ref, WeakIdRef) + assert stored_ref() is key_obj # Verify the weak ref points to our key + + def test_setitem_overwrites_existing_key(self): + """Test that __setitem__ overwrites existing key with new value""" + + class TestKey: + def __init__(self, value): + self.value = value + + key_obj = TestKey("test_key") + initial_value = "initial_value" + new_value = "new_value" + + weak_dict = WeakIdKeyDictionary() + weak_dict[key_obj] = initial_value + assert weak_dict[key_obj] == initial_value + + # Overwrite with new value + weak_dict[key_obj] = new_value + assert weak_dict[key_obj] == new_value + assert len(weak_dict.data) == 1 # Still only one entry + + def test_setitem_with_custom_ref_type(self): + """Test that __setitem__ uses the custom ref_type when provided""" + + class TestKey: + def __init__(self, value): + self.value = value + + key_obj = TestKey("test_key") + value = "test_value" + + # Create a custom ref_type that tracks calls + class TrackingRefType: + def __init__(self): + self.calls = [] + + def __call__(self, key, callback=None): + self.calls.append((key, callback)) + return WeakIdRef(key, callback) + + tracking_ref_type = TrackingRefType() + + # Create dictionary with custom ref_type + weak_dict = WeakIdKeyDictionary(ref_type=tracking_ref_type) + weak_dict[key_obj] = value + + # Verify custom ref_type was called + assert len(tracking_ref_type.calls) == 1 + called_key, called_callback = tracking_ref_type.calls[0] + assert called_key is key_obj + assert called_callback is weak_dict._remove + + # Verify value was stored correctly + assert weak_dict[key_obj] == value + + def test_setitem_cleanup_after_key_garbage_collected(self): + """Test that setting an item and then garbage collecting the key cleans up properly""" + import gc + + weak_dict = WeakIdKeyDictionary() + + # Create key in separate scope to allow garbage collection + def create_and_set(): + class TestKey: + def __init__(self, value): + self.value = value + + key_obj = TestKey("test_key") + value = "test_value" + weak_dict[key_obj] = value + return weakref.ref(key_obj) + + key_ref = create_and_set() + + # Force garbage collection to clean up the key + gc.collect() + + # The dictionary should be empty after key is garbage collected + assert len(weak_dict) == 0 + assert len(weak_dict.data) == 0 diff --git a/tests/test_coverup_113.py b/tests/test_coverup_113.py new file mode 100644 index 0000000..ff6dbd5 --- /dev/null +++ b/tests/test_coverup_113.py @@ -0,0 +1,92 @@ +# file: objwatch/targets.py:464-487 +# asked: {"lines": [464, 487], "branches": []} +# gained: {"lines": [464, 487], "branches": []} + +import pytest +from objwatch.targets import Targets + + +class TestTargetsGetExcludeTargets: + """Test cases for Targets.get_exclude_targets method.""" + + def test_get_exclude_targets_empty(self, monkeypatch): + """Test get_exclude_targets with empty exclude targets.""" + + # Mock the _parse_target method to avoid actual module imports + def mock_parse_target(self, target): + if target == "builtins": + return "builtins", {"classes": {}, "functions": [], "globals": []} + return target, {"classes": {}, "functions": [], "globals": []} + + monkeypatch.setattr(Targets, "_parse_target", mock_parse_target) + targets = Targets(targets=["builtins"]) + result = targets.get_exclude_targets() + assert isinstance(result, dict) + assert result == {} + + def test_get_exclude_targets_with_exclusions(self, monkeypatch): + """Test get_exclude_targets with populated exclude targets.""" + exclude_targets = { + "test.module": { + "classes": {"TestClass": {"methods": ["method1", "method2"], "attributes": ["attr1", "attr2"]}}, + "functions": ["func1", "func2"], + "globals": ["GLOBAL_VAR1", "GLOBAL_VAR2"], + } + } + + def mock_parse_target(self, target): + if target == "builtins": + return "builtins", {"classes": {}, "functions": [], "globals": []} + return target, {"classes": {}, "functions": [], "globals": []} + + def mock_process_targets(self, targets): + # Return the exclude_targets directly without processing + if targets == exclude_targets: + return exclude_targets + return {} + + monkeypatch.setattr(Targets, "_parse_target", mock_parse_target) + monkeypatch.setattr(Targets, "_process_targets", mock_process_targets) + targets = Targets(targets=["builtins"], exclude_targets=exclude_targets) + result = targets.get_exclude_targets() + assert isinstance(result, dict) + assert result == exclude_targets + assert "test.module" in result + assert "classes" in result["test.module"] + assert "TestClass" in result["test.module"]["classes"] + assert "methods" in result["test.module"]["classes"]["TestClass"] + assert "attributes" in result["test.module"]["classes"]["TestClass"] + assert "functions" in result["test.module"] + assert "globals" in result["test.module"] + + def test_get_exclude_targets_multiple_modules(self, monkeypatch): + """Test get_exclude_targets with multiple modules in exclude targets.""" + exclude_targets = { + "module1": {"functions": ["func1"], "globals": ["global1"]}, + "module2": {"classes": {"ClassA": {"methods": ["method_a"], "attributes": ["attr_a"]}}}, + } + + def mock_parse_target(self, target): + if target == "builtins": + return "builtins", {"classes": {}, "functions": [], "globals": []} + return target, {"classes": {}, "functions": [], "globals": []} + + def mock_process_targets(self, targets): + # Return the exclude_targets directly without processing + if targets == exclude_targets: + return exclude_targets + return {} + + monkeypatch.setattr(Targets, "_parse_target", mock_parse_target) + monkeypatch.setattr(Targets, "_process_targets", mock_process_targets) + targets = Targets(targets=["builtins"], exclude_targets=exclude_targets) + result = targets.get_exclude_targets() + assert isinstance(result, dict) + assert result == exclude_targets + assert "module1" in result + assert "module2" in result + assert result["module1"]["functions"] == ["func1"] + assert result["module1"]["globals"] == ["global1"] + assert "ClassA" in result["module2"]["classes"] + assert result["module2"]["classes"]["ClassA"]["methods"] == ["method_a"] + assert result["module2"]["classes"]["ClassA"]["attributes"] == ["attr_a"] diff --git a/tests/test_coverup_114.py b/tests/test_coverup_114.py new file mode 100644 index 0000000..9cfc701 --- /dev/null +++ b/tests/test_coverup_114.py @@ -0,0 +1,142 @@ +# file: objwatch/utils/weak.py:226-232 +# asked: {"lines": [226, 227, 228, 229, 230, 231, 232], "branches": [[227, 228], [227, 232]]} +# gained: {"lines": [226, 227, 228, 229, 230, 231, 232], "branches": [[227, 228], [227, 232]]} + +import pytest +from collections.abc import Mapping +from collections import OrderedDict + + +class TestWeakIdKeyDictionaryRor: + + def test_ror_with_mapping(self): + """Test __ror__ with a regular mapping object containing only weak-referenceable objects""" + from objwatch.utils.weak import WeakIdKeyDictionary + + # Create a WeakIdKeyDictionary with some data + # Use objects that can be weakly referenced (like custom classes) + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + obj3 = TestObject("obj3") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + + # Create a regular mapping with only weak-referenceable objects + regular_dict = {obj1: "regular_value1", obj3: "value3"} + + # Test the reverse or operation: weak_dict.__ror__(regular_dict) + # This is called when doing: regular_dict | weak_dict + result = weak_dict.__ror__(regular_dict) + + # The result should be a new WeakIdKeyDictionary containing merged data + # Note: The order of updates matters - self.update(other) then self.update(self) + # So self values should take precedence over other values + assert isinstance(result, WeakIdKeyDictionary) + assert result[obj1] == "value1" # weak_dict value takes precedence (self.update(self) last) + assert result[obj2] == "value2" # weak_dict value + assert result[obj3] == "value3" # regular_dict value + + # Clean up - ensure no references remain + del obj1, obj2, obj3, regular_dict, weak_dict, result + + def test_ror_with_non_mapping(self): + """Test __ror__ with a non-mapping object returns NotImplemented""" + from objwatch.utils.weak import WeakIdKeyDictionary + + weak_dict = WeakIdKeyDictionary() + + # Test with a non-mapping object (list) + non_mapping = [1, 2, 3] + result = weak_dict.__ror__(non_mapping) + + # Should return NotImplemented for non-mapping types + assert result is NotImplemented + + # Clean up + del weak_dict, non_mapping + + def test_ror_with_ordered_dict(self): + """Test __ror__ with OrderedDict containing only weak-referenceable objects""" + from objwatch.utils.weak import WeakIdKeyDictionary + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + obj3 = TestObject("obj3") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "weak_value1" + weak_dict[obj2] = "weak_value2" + + # Use OrderedDict with only weak-referenceable objects + ordered_dict = OrderedDict([(obj1, "ordered_value1"), (obj3, "value3")]) + + result = weak_dict.__ror__(ordered_dict) + + assert isinstance(result, WeakIdKeyDictionary) + assert result[obj1] == "weak_value1" # weak_dict value takes precedence (self.update(self) last) + assert result[obj2] == "weak_value2" # weak_dict value + assert result[obj3] == "value3" # ordered_dict value + + # Clean up + del obj1, obj2, obj3, weak_dict, ordered_dict, result + + def test_ror_empty_weak_dict(self): + """Test __ror__ when WeakIdKeyDictionary is empty""" + from objwatch.utils.weak import WeakIdKeyDictionary + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + + weak_dict = WeakIdKeyDictionary() + regular_dict = {obj1: "value1", obj2: "value2"} + + result = weak_dict.__ror__(regular_dict) + + assert isinstance(result, WeakIdKeyDictionary) + assert len(result) == 2 + assert result[obj1] == "value1" + assert result[obj2] == "value2" + + # Clean up + del obj1, obj2, weak_dict, regular_dict, result + + def test_ror_empty_other_dict(self): + """Test __ror__ when the other mapping is empty""" + from objwatch.utils.weak import WeakIdKeyDictionary + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + + empty_dict = {} + + result = weak_dict.__ror__(empty_dict) + + assert isinstance(result, WeakIdKeyDictionary) + assert len(result) == 2 + assert result[obj1] == "value1" + assert result[obj2] == "value2" + + # Clean up + del obj1, obj2, weak_dict, empty_dict, result diff --git a/tests/test_coverup_115.py b/tests/test_coverup_115.py new file mode 100644 index 0000000..014efa8 --- /dev/null +++ b/tests/test_coverup_115.py @@ -0,0 +1,196 @@ +# file: objwatch/tracer.py:374-421 +# asked: {"lines": [374, 384, 385, 387, 390, 391, 394, 395, 396, 399, 400, 403, 404, 407, 408, 409, 410, 411, 413, 415, 417, 418, 419, 421], "branches": [[384, 385], [384, 387], [390, 391], [390, 394], [394, 395], [394, 417], [403, 404], [403, 407], [407, 408], [407, 415], [418, 419], [418, 421]]} +# gained: {"lines": [374, 384, 385, 387, 390, 391, 394, 395, 396, 399, 400, 403, 404, 407, 408, 409, 410, 411, 413, 417, 418, 419, 421], "branches": [[384, 385], [384, 387], [390, 391], [390, 394], [394, 395], [394, 417], [403, 404], [403, 407], [407, 408], [418, 419], [418, 421]]} + +import pytest +import sys +from types import FrameType +from unittest.mock import Mock, patch +from objwatch.config import ObjWatchConfig + + +class TestTracerShouldTraceFrame: + """Test cases for Tracer._should_trace_frame method to achieve full coverage.""" + + def test_filename_endswith_returns_true(self, monkeypatch): + """Test when _filename_endswith returns True (line 384-385).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + + # Mock _filename_endswith to return True + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: False) + + result = tracer._should_trace_frame(mock_frame) + assert result is True + + def test_module_not_traced_returns_false(self, monkeypatch): + """Test when module is not traced (line 390-391).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + + # Mock _filename_endswith to return False, _should_trace_module to return False + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: False) + + result = tracer._should_trace_frame(mock_frame) + assert result is False + + def test_class_method_traced_returns_true(self, monkeypatch): + """Test when class and method are both traced (line 394-404).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + + # Create a simple object with __class__ attribute + class TestClass: + pass + + test_obj = TestClass() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_name = "test_method" + + # Mock dependencies + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_class', lambda x, y: True) + monkeypatch.setattr(tracer, '_should_trace_method', lambda x, y, z: True) + + result = tracer._should_trace_frame(mock_frame) + assert result is True + + def test_class_traced_attributes_traced_returns_true(self, monkeypatch): + """Test when class is traced and attributes are traced (line 407-413).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + + # Create a simple object with __class__ and __dict__ attributes + class TestClass: + def __init__(self): + self.attr1 = 'value1' + self.attr2 = 'value2' + + test_obj = TestClass() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_name = "test_method" + + # Mock dependencies + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_class', lambda x, y: True) + monkeypatch.setattr(tracer, '_should_trace_method', lambda x, y, z: False) + monkeypatch.setattr(tracer, '_should_trace_attribute', lambda x, y, z: True) + + result = tracer._should_trace_frame(mock_frame) + assert result is True + + def test_class_traced_no_attributes_traced_returns_false(self, monkeypatch): + """Test when class is traced but no attributes are traced (line 407-415).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + + # Create a simple object with __class__ and __dict__ attributes + class TestClass: + def __init__(self): + self.attr1 = 'value1' + self.attr2 = 'value2' + + test_obj = TestClass() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_name = "test_method" + + # Mock dependencies + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_class', lambda x, y: True) + monkeypatch.setattr(tracer, '_should_trace_method', lambda x, y, z: False) + monkeypatch.setattr(tracer, '_should_trace_attribute', lambda x, y, z: False) + + result = tracer._should_trace_frame(mock_frame) + assert result is False + + def test_function_traced_returns_true(self, monkeypatch): + """Test when function is traced (line 417-419).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + mock_frame.f_locals = {} # No 'self' for function case + mock_frame.f_code.co_name = "test_function" + + # Mock dependencies + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_function', lambda x, y: True) + + result = tracer._should_trace_frame(mock_frame) + assert result is True + + def test_check_global_changes_returns_true(self, monkeypatch): + """Test when _check_global_changes returns True (line 421).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + mock_frame.f_locals = {} # No 'self' for function case + mock_frame.f_code.co_name = "test_function" + + # Mock dependencies + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_function', lambda x, y: False) + monkeypatch.setattr(tracer, '_check_global_changes', lambda x: True) + + result = tracer._should_trace_frame(mock_frame) + assert result is True + + def test_check_global_changes_returns_false(self, monkeypatch): + """Test when _check_global_changes returns False (line 421).""" + from objwatch.tracer import Tracer + + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': 'test_module'} + mock_frame.f_locals = {} # No 'self' for function case + mock_frame.f_code.co_name = "test_function" + + # Mock dependencies + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + monkeypatch.setattr(tracer, '_should_trace_module', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_function', lambda x, y: False) + monkeypatch.setattr(tracer, '_check_global_changes', lambda x: False) + + result = tracer._should_trace_frame(mock_frame) + assert result is False diff --git a/tests/test_coverup_116.py b/tests/test_coverup_116.py new file mode 100644 index 0000000..13a0549 --- /dev/null +++ b/tests/test_coverup_116.py @@ -0,0 +1,198 @@ +# file: objwatch/wrappers/abc_wrapper.py:133-144 +# asked: {"lines": [133, 143, 144], "branches": []} +# gained: {"lines": [133, 143, 144], "branches": []} + +import pytest +from unittest.mock import Mock, MagicMock +from objwatch.wrappers.abc_wrapper import ABCWrapper +from objwatch.constants import Constants +from objwatch.event_handls import EventHandls +from types import FunctionType +from enum import Enum + + +class TestABCWrapper: + """Test class for ABCWrapper's _format_return method.""" + + class ConcreteWrapper(ABCWrapper): + """Concrete implementation for testing abstract methods.""" + + def wrap_call(self, func_name, frame): + return f"call_{func_name}" + + def wrap_return(self, func_name, result): + return f"return_{func_name}" + + def wrap_upd(self, old_value, current_value): + return f"upd_old", f"upd_current" + + def test_format_return_with_basic_types(self): + """Test _format_return with basic types (bool, int, float, str, None).""" + wrapper = self.ConcreteWrapper() + + # Test with boolean + result = wrapper._format_return(True) + assert result == "True" + + # Test with integer + result = wrapper._format_return(42) + assert result == "42" + + # Test with float + result = wrapper._format_return(3.14) + assert result == "3.14" + + # Test with string + result = wrapper._format_return("hello") + assert result == "hello" + + # Test with None + result = wrapper._format_return(None) + assert result == "None" + + def test_format_return_with_function_type(self): + """Test _format_return with FunctionType.""" + wrapper = self.ConcreteWrapper() + + def test_func(): + pass + + result = wrapper._format_return(test_func) + # FunctionType is in LOG_ELEMENT_TYPES, so it should return the string representation + assert "test_func" in result + + def test_format_return_with_enum_type(self): + """Test _format_return with Enum type.""" + wrapper = self.ConcreteWrapper() + + class TestEnum(Enum): + VALUE1 = 1 + VALUE2 = 2 + + result = wrapper._format_return(TestEnum.VALUE1) + assert "VALUE1" in result + + def test_format_return_with_list_sequence(self, monkeypatch): + """Test _format_return with list sequence.""" + wrapper = self.ConcreteWrapper() + + # Mock format_sequence to return a formatted string + mock_format_sequence = Mock(return_value="(list)[1, 2, 3]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + test_list = [1, 2, 3] + result = wrapper._format_return(test_list) + + mock_format_sequence.assert_called_once_with(test_list, func=wrapper.format_sequence_func) + assert result == "[(list)[1, 2, 3]]" + + def test_format_return_with_empty_list_sequence(self, monkeypatch): + """Test _format_return with empty list sequence.""" + wrapper = self.ConcreteWrapper() + + # Mock format_sequence to return empty list format + mock_format_sequence = Mock(return_value="(list)[]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + test_list = [] + result = wrapper._format_return(test_list) + + mock_format_sequence.assert_called_once_with(test_list, func=wrapper.format_sequence_func) + assert result == "[(list)[]]" + + def test_format_return_with_set_sequence(self, monkeypatch): + """Test _format_return with set sequence.""" + wrapper = self.ConcreteWrapper() + + # Mock format_sequence to return a formatted string + mock_format_sequence = Mock(return_value="(set)[1, 2, 3]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + test_set = {1, 2, 3} + result = wrapper._format_return(test_set) + + mock_format_sequence.assert_called_once_with(test_set, func=wrapper.format_sequence_func) + assert result == "[(set)[1, 2, 3]]" + + def test_format_return_with_tuple_sequence(self, monkeypatch): + """Test _format_return with tuple sequence.""" + wrapper = self.ConcreteWrapper() + + # Mock format_sequence to return a formatted string + mock_format_sequence = Mock(return_value="(tuple)[1, 2, 3]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + test_tuple = (1, 2, 3) + result = wrapper._format_return(test_tuple) + + mock_format_sequence.assert_called_once_with(test_tuple, func=wrapper.format_sequence_func) + assert result == "[(tuple)[1, 2, 3]]" + + def test_format_return_with_dict_sequence(self, monkeypatch): + """Test _format_return with dict sequence.""" + wrapper = self.ConcreteWrapper() + + # Mock format_sequence to return a formatted string + mock_format_sequence = Mock(return_value="(dict)[('a', 1), ('b', 2)]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + test_dict = {'a': 1, 'b': 2} + result = wrapper._format_return(test_dict) + + mock_format_sequence.assert_called_once_with(test_dict, func=wrapper.format_sequence_func) + assert result == "[(dict)[('a', 1), ('b', 2)]]" + + def test_format_return_with_sequence_format_sequence_func(self, monkeypatch): + """Test _format_return with sequence when format_sequence_func is set.""" + wrapper = self.ConcreteWrapper() + wrapper.format_sequence_func = lambda x: [str(i * 2) for i in x] + + # Mock format_sequence to use the custom function + def mock_format_sequence(seq, max_elements=None, func=None): + if func: + return f"(list){func(seq)}" + return f"(list){seq}" + + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + test_list = [1, 2, 3] + result = wrapper._format_return(test_list) + + assert result == "[(list)['2', '4', '6']]" + + def test_format_return_with_sequence_empty_format_result(self, monkeypatch): + """Test _format_return with sequence when format_sequence returns empty string.""" + wrapper = self.ConcreteWrapper() + + # Mock format_sequence to return empty string + mock_format_sequence = Mock(return_value="") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + test_list = [1, 2, 3] + result = wrapper._format_return(test_list) + + mock_format_sequence.assert_called_once_with(test_list, func=wrapper.format_sequence_func) + # When format_sequence returns empty string, it should fall back to (type)list + assert result == "[(type)list]" + + def test_format_return_with_custom_object(self): + """Test _format_return with custom object that has __name__ attribute.""" + wrapper = self.ConcreteWrapper() + + class CustomClass: + __name__ = "CustomClass" + + custom_obj = CustomClass() + result = wrapper._format_return(custom_obj) + assert result == "(type)CustomClass" + + def test_format_return_with_object_no_name(self): + """Test _format_return with object that doesn't have __name__ attribute.""" + wrapper = self.ConcreteWrapper() + + class CustomClass: + pass + + custom_obj = CustomClass() + result = wrapper._format_return(custom_obj) + assert result == "(type)CustomClass" diff --git a/tests/test_coverup_117.py b/tests/test_coverup_117.py new file mode 100644 index 0000000..a7c733a --- /dev/null +++ b/tests/test_coverup_117.py @@ -0,0 +1,255 @@ +# file: objwatch/core.py:19-68 +# asked: {"lines": [19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 65, 68], "branches": []} +# gained: {"lines": [19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 65, 68], "branches": []} + +import pytest +import logging +import tempfile +import os +from types import ModuleType +from typing import List, Union, Optional +from unittest.mock import Mock, patch +from objwatch.core import ObjWatch +from objwatch.config import ObjWatchConfig +from objwatch.tracer import Tracer +from objwatch.wrappers import ABCWrapper +from objwatch.utils.logger import create_logger + + +class TestObjWatchInit: + """Test cases for ObjWatch.__init__ method to achieve full coverage.""" + + def test_init_with_minimal_parameters(self): + """Test ObjWatch initialization with only required parameters.""" + targets = ["test_module"] + objwatch = ObjWatch(targets=targets) + + assert isinstance(objwatch.tracer, Tracer) + assert objwatch.tracer.config.targets == targets + assert objwatch.tracer.config.exclude_targets is None + assert objwatch.tracer.config.framework is None + assert objwatch.tracer.config.indexes is None + assert objwatch.tracer.config.output is None + assert objwatch.tracer.config.output_xml is None + assert objwatch.tracer.config.level == logging.DEBUG + assert objwatch.tracer.config.simple is False + assert objwatch.tracer.config.wrapper is None + assert objwatch.tracer.config.with_locals is False + assert objwatch.tracer.config.with_globals is False + + def test_init_with_all_parameters_except_wrapper(self): + """Test ObjWatch initialization with all parameters except wrapper.""" + targets = ["test_module"] + exclude_targets = ["excluded_module"] + framework = "multiprocessing" + indexes = [1, 2, 3] + output = "test.log" + output_xml = "test.xml" + level = logging.INFO + simple = True + with_locals = True + with_globals = True + + objwatch = ObjWatch( + targets=targets, + exclude_targets=exclude_targets, + framework=framework, + indexes=indexes, + output=output, + output_xml=output_xml, + level=level, + simple=simple, + wrapper=None, + with_locals=with_locals, + with_globals=with_globals, + ) + + assert isinstance(objwatch.tracer, Tracer) + assert objwatch.tracer.config.targets == targets + assert objwatch.tracer.config.exclude_targets == exclude_targets + assert objwatch.tracer.config.framework == framework + assert objwatch.tracer.config.indexes == indexes + assert objwatch.tracer.config.output == output + assert objwatch.tracer.config.output_xml == output_xml + assert objwatch.tracer.config.level == level + assert objwatch.tracer.config.simple == simple + assert objwatch.tracer.config.wrapper is None + assert objwatch.tracer.config.with_locals == with_locals + assert objwatch.tracer.config.with_globals == with_globals + + def test_init_with_module_type_targets(self): + """Test ObjWatch initialization with ModuleType targets.""" + test_module = ModuleType("test_module") + targets = [test_module] + + objwatch = ObjWatch(targets=targets) + + assert isinstance(objwatch.tracer, Tracer) + assert objwatch.tracer.config.targets == targets + + def test_init_with_mixed_target_types(self): + """Test ObjWatch initialization with mixed string and ModuleType targets.""" + test_module = ModuleType("test_module") + targets = ["string_target", test_module] + + objwatch = ObjWatch(targets=targets) + + assert isinstance(objwatch.tracer, Tracer) + assert objwatch.tracer.config.targets == targets + + def test_init_with_file_output(self, tmp_path): + """Test ObjWatch initialization with file output.""" + log_file = tmp_path / "test.log" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, output=str(log_file)) + + assert isinstance(objwatch.tracer, Tracer) + assert objwatch.tracer.config.output == str(log_file) + + def test_init_with_different_log_levels(self): + """Test ObjWatch initialization with different logging levels.""" + targets = ["test_module"] + + for level in [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR]: + objwatch = ObjWatch(targets=targets, level=level) + assert objwatch.tracer.config.level == level + + def test_init_with_simple_logging(self): + """Test ObjWatch initialization with simple logging enabled.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, simple=True) + + assert objwatch.tracer.config.simple is True + + def test_init_with_none_exclude_targets(self): + """Test ObjWatch initialization with explicit None exclude_targets.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, exclude_targets=None) + + assert objwatch.tracer.config.exclude_targets is None + + def test_init_with_empty_exclude_targets(self): + """Test ObjWatch initialization with empty exclude_targets list.""" + targets = ["test_module"] + exclude_targets = [] + + objwatch = ObjWatch(targets=targets, exclude_targets=exclude_targets) + + assert objwatch.tracer.config.exclude_targets == exclude_targets + + def test_init_with_none_indexes(self): + """Test ObjWatch initialization with explicit None indexes.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, indexes=None) + + assert objwatch.tracer.config.indexes is None + + def test_init_with_empty_indexes(self): + """Test ObjWatch initialization with empty indexes list.""" + targets = ["test_module"] + indexes = [] + + objwatch = ObjWatch(targets=targets, indexes=indexes) + + assert objwatch.tracer.config.indexes == indexes + + def test_init_with_none_wrapper(self): + """Test ObjWatch initialization with explicit None wrapper.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, wrapper=None) + + assert objwatch.tracer.config.wrapper is None + + def test_init_with_locals_tracking(self): + """Test ObjWatch initialization with locals tracking enabled.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, with_locals=True) + + assert objwatch.tracer.config.with_locals is True + + def test_init_with_globals_tracking(self): + """Test ObjWatch initialization with globals tracking enabled.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, with_globals=True) + + assert objwatch.tracer.config.with_globals is True + + def test_init_with_both_locals_and_globals_tracking(self): + """Test ObjWatch initialization with both locals and globals tracking enabled.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets, with_locals=True, with_globals=True) + + assert objwatch.tracer.config.with_locals is True + assert objwatch.tracer.config.with_globals is True + + @patch('objwatch.core.create_logger') + def test_create_logger_called_with_config(self, mock_create_logger): + """Test that create_logger is called with configuration parameters.""" + targets = ["test_module"] + output = "test.log" + level = logging.INFO + simple = True + + objwatch = ObjWatch(targets=targets, output=output, level=level, simple=simple) + + mock_create_logger.assert_called_once_with(output=output, level=level, simple=simple) + + @patch('objwatch.core.Tracer') + def test_tracer_initialized_with_config(self, mock_tracer): + """Test that Tracer is initialized with the configuration.""" + targets = ["test_module"] + + objwatch = ObjWatch(targets=targets) + + # Get the config passed to Tracer + call_args = mock_tracer.call_args + assert call_args is not None + config_arg = call_args[1]['config'] # kwargs are in position 1 + assert isinstance(config_arg, ObjWatchConfig) + assert config_arg.targets == targets + + def test_init_with_valid_wrapper_class(self): + """Test ObjWatch initialization with a valid wrapper class.""" + targets = ["test_module"] + + # Create a valid wrapper class implementing all abstract methods + class ValidWrapper(ABCWrapper): + def wrap_call(self, func_name: str, frame): + return f"Call: {func_name}" + + def wrap_return(self, func_name: str, result): + return f"Return: {func_name}" + + def wrap_upd(self, old_value, current_value): + return f"Old: {old_value}", f"New: {current_value}" + + objwatch = ObjWatch(targets=targets, wrapper=ValidWrapper) + + assert isinstance(objwatch.tracer, Tracer) + assert objwatch.tracer.config.wrapper == ValidWrapper + + @patch('objwatch.core.Tracer') + def test_init_with_wrapper_instance(self, mock_tracer): + """Test ObjWatch initialization with a wrapper instance.""" + targets = ["test_module"] + + # Create a mock wrapper instance + wrapper_instance = Mock(spec=ABCWrapper) + + objwatch = ObjWatch(targets=targets, wrapper=wrapper_instance) + + # When Tracer is mocked, objwatch.tracer is the mock, not a real Tracer + assert objwatch.tracer is mock_tracer.return_value + # Verify Tracer was called with the correct config + mock_tracer.assert_called_once() + call_args = mock_tracer.call_args + config_arg = call_args[1]['config'] + assert config_arg.wrapper == wrapper_instance diff --git a/tests/test_coverup_118.py b/tests/test_coverup_118.py new file mode 100644 index 0000000..716e134 --- /dev/null +++ b/tests/test_coverup_118.py @@ -0,0 +1,144 @@ +# file: objwatch/wrappers/abc_wrapper.py:64-84 +# asked: {"lines": [64, 74, 75, 76, 77, 78, 79, 80, 82, 83, 84], "branches": [[78, 79], [78, 82], [79, 78], [79, 80], [82, 83], [82, 84]]} +# gained: {"lines": [64, 74, 75, 76, 77, 78, 79, 80, 82, 83, 84], "branches": [[78, 79], [78, 82], [79, 78], [79, 80], [82, 83], [82, 84]]} + +import pytest +import sys +from types import FrameType, CodeType +from typing import Any, List, Tuple +from objwatch.wrappers.abc_wrapper import ABCWrapper + + +class TestABCWrapper(ABCWrapper): + """Concrete implementation for testing abstract methods""" + + def wrap_call(self, func_name: str, frame: FrameType) -> str: + return f"call:{func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + return f"return:{func_name}:{result}" + + def wrap_upd(self, old_value: Any, current_value: Any) -> Tuple[str, str]: + return f"old:{old_value}", f"new:{current_value}" + + +class MockFrame: + """Mock frame object for testing""" + + def __init__(self, arg_names: List[str], local_vars: dict, has_varkw: bool = False): + self.f_locals = local_vars.copy() + self.f_code = MockCode(arg_names, has_varkw) + + +class MockCode: + """Mock code object for testing""" + + def __init__(self, arg_names: List[str], has_varkw: bool = False): + self.co_varnames = tuple(arg_names) + self.co_argcount = len(arg_names) + self.co_flags = 0x08 if has_varkw else 0 + + +def test_extract_args_kwargs_basic(): + """Test basic argument extraction without varargs/varkw""" + wrapper = TestABCWrapper() + + # Create frame with basic arguments + arg_names = ['a', 'b', 'c'] + local_vars = {'a': 1, 'b': 2, 'c': 3} + frame = MockFrame(arg_names, local_vars) + + args, kwargs = wrapper._extract_args_kwargs(frame) + + assert args == [1, 2, 3] + assert kwargs == {} + + +def test_extract_args_kwargs_with_varkw(): + """Test argument extraction with variable keyword arguments (CO_VARKEYWORDS)""" + wrapper = TestABCWrapper() + + # Create frame with varkw flag and extra keyword arguments + arg_names = ['a', 'b'] + local_vars = {'a': 1, 'b': 2, 'extra1': 'value1', 'extra2': 'value2'} + frame = MockFrame(arg_names, local_vars, has_varkw=True) + + args, kwargs = wrapper._extract_args_kwargs(frame) + + assert args == [1, 2] + assert kwargs == {'extra1': 'value1', 'extra2': 'value2'} + + +def test_extract_args_kwargs_with_varkw_ignores_private(): + """Test that varkw extraction ignores private variables (starting with _)""" + wrapper = TestABCWrapper() + + # Create frame with varkw flag and mixed public/private variables + arg_names = ['a', 'b'] + local_vars = {'a': 1, 'b': 2, 'public': 'value1', '_private': 'value2', '__dunder': 'value3'} + frame = MockFrame(arg_names, local_vars, has_varkw=True) + + args, kwargs = wrapper._extract_args_kwargs(frame) + + assert args == [1, 2] + assert kwargs == {'public': 'value1'} # _private and __dunder should be excluded + + +def test_extract_args_kwargs_missing_locals(): + """Test when some arguments are not in frame.f_locals""" + wrapper = TestABCWrapper() + + # Create frame where some arguments are missing from locals + arg_names = ['a', 'b', 'c', 'd'] + local_vars = {'a': 1, 'c': 3} # b and d are missing + frame = MockFrame(arg_names, local_vars) + + args, kwargs = wrapper._extract_args_kwargs(frame) + + assert args == [1, 3] # Only a and c should be included + assert kwargs == {} + + +def test_extract_args_kwargs_no_varkw_flag(): + """Test that kwargs are empty when CO_VARKEYWORDS flag is not set""" + wrapper = TestABCWrapper() + + # Create frame with extra variables but no varkw flag + arg_names = ['a', 'b'] + local_vars = {'a': 1, 'b': 2, 'extra': 'value'} + frame = MockFrame(arg_names, local_vars, has_varkw=False) + + args, kwargs = wrapper._extract_args_kwargs(frame) + + assert args == [1, 2] + assert kwargs == {} # Should be empty without varkw flag + + +def test_extract_args_kwargs_empty_args(): + """Test with empty argument list""" + wrapper = TestABCWrapper() + + # Create frame with no arguments + arg_names = [] + local_vars = {'other_var': 'value'} + frame = MockFrame(arg_names, local_vars, has_varkw=True) + + args, kwargs = wrapper._extract_args_kwargs(frame) + + assert args == [] + assert kwargs == {'other_var': 'value'} # All locals become kwargs with varkw flag + + +def test_extract_args_kwargs_mixed_case(): + """Test with mixed case variable names""" + wrapper = TestABCWrapper() + + # Create frame with mixed case names + arg_names = ['myVar', 'another_var'] + local_vars = {'myVar': 1, 'another_var': 2, 'ExtraVar': 'value1', '_privateVar': 'value2'} + frame = MockFrame(arg_names, local_vars, has_varkw=True) + + args, kwargs = wrapper._extract_args_kwargs(frame) + + assert args == [1, 2] + assert kwargs == {'ExtraVar': 'value1'} # _privateVar should be excluded diff --git a/tests/test_coverup_119.py b/tests/test_coverup_119.py new file mode 100644 index 0000000..f385b1a --- /dev/null +++ b/tests/test_coverup_119.py @@ -0,0 +1,225 @@ +# file: objwatch/tracer.py:560-602 +# asked: {"lines": [560, 568, 569, 571, 572, 573, 575, 576, 577, 578, 579, 581, 582, 583, 585, 586, 587, 588, 590, 591, 592, 593, 594, 595, 596, 597, 600, 601, 602], "branches": [[568, 569], [568, 571], [575, 0], [575, 576], [581, 0], [581, 582], [582, 583], [582, 585], [601, 581], [601, 602]]} +# gained: {"lines": [560, 568, 569, 571, 572, 573, 575, 576, 577, 578, 579, 581, 582, 583, 585, 586, 587, 588, 590, 591, 592, 593, 594, 595, 596, 597, 600, 601, 602], "branches": [[568, 569], [568, 571], [575, 0], [575, 576], [581, 0], [581, 582], [582, 583], [582, 585], [601, 581], [601, 602]]} + +import pytest +from unittest.mock import Mock, MagicMock, patch +import types +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig +from objwatch.constants import Constants + + +class TestObject: + def __init__(self): + self.regular_attr = "initial" + self.list_attr = [1, 2, 3] + self.dict_attr = {"a": 1, "b": 2} + self.tuple_attr = (1, 2, 3) + self.set_attr = {1, 2, 3} + + +class TestTrackObjectChange: + + def test_track_object_change_no_self_in_locals(self, monkeypatch): + """Test that method returns early when 'self' not in frame.f_locals""" + config = ObjWatchConfig(targets=["test_file.py"]) + tracer = Tracer(config) + mock_frame = Mock() + mock_frame.f_locals = {} + + tracer._track_object_change(mock_frame, 100) + + # No assertions needed - just verifying no exceptions and early return + + def test_track_object_change_object_not_tracked(self, monkeypatch): + """Test that method returns early when object is not in tracked_objects""" + config = ObjWatchConfig(targets=["test_file.py"]) + tracer = Tracer(config) + tracer.tracked_objects = {} + tracer.tracked_objects_lens = {} + + test_obj = TestObject() + mock_frame = Mock() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': '__main__'} + + tracer._track_object_change(mock_frame, 100) + + # No assertions needed - just verifying no exceptions and early return + + def test_track_object_change_with_sequence_types(self, monkeypatch): + """Test tracking changes with sequence types (list, dict, tuple, set)""" + config = ObjWatchConfig(targets=["test_file.py"]) + tracer = Tracer(config) + + # Setup tracked object + test_obj = TestObject() + tracer.tracked_objects = { + test_obj: {'list_attr': [1, 2], 'dict_attr': {'a': 1}, 'tuple_attr': (1, 2), 'set_attr': {1, 2}} + } + tracer.tracked_objects_lens = {test_obj: {'list_attr': 2, 'dict_attr': 1, 'tuple_attr': 2, 'set_attr': 2}} + + # Mock dependencies + mock_frame = Mock() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': '__main__'} + + # Mock _filename_endswith to return True (trace all attrs) + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_attribute', lambda m, c, a: True) + + # Mock _handle_change_type to verify it's called correctly + mock_handle_change = Mock() + monkeypatch.setattr(tracer, '_handle_change_type', mock_handle_change) + + tracer._track_object_change(mock_frame, 100) + + # Verify _handle_change_type was called for each attribute (including regular_attr) + assert mock_handle_change.call_count == 5 + + # Verify tracked objects were updated + assert tracer.tracked_objects[test_obj]['regular_attr'] == 'initial' + assert tracer.tracked_objects[test_obj]['list_attr'] == [1, 2, 3] + assert tracer.tracked_objects[test_obj]['dict_attr'] == {"a": 1, "b": 2} + assert tracer.tracked_objects[test_obj]['tuple_attr'] == (1, 2, 3) + assert tracer.tracked_objects[test_obj]['set_attr'] == {1, 2, 3} + + # Verify tracked objects lens were updated for sequence types + assert tracer.tracked_objects_lens[test_obj]['list_attr'] == 3 + assert tracer.tracked_objects_lens[test_obj]['dict_attr'] == 2 + assert tracer.tracked_objects_lens[test_obj]['tuple_attr'] == 3 + assert tracer.tracked_objects_lens[test_obj]['set_attr'] == 3 + # regular_attr should not have lens entry + assert 'regular_attr' not in tracer.tracked_objects_lens[test_obj] + + def test_track_object_change_with_filtered_attributes(self, monkeypatch): + """Test that attributes are filtered based on _should_trace_attribute""" + config = ObjWatchConfig(targets=["test_file.py"]) + tracer = Tracer(config) + + test_obj = TestObject() + tracer.tracked_objects = {test_obj: {'regular_attr': 'old', 'list_attr': [1, 2]}} + tracer.tracked_objects_lens = {test_obj: {'list_attr': 2}} + + mock_frame = Mock() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': '__main__'} + + # Mock _filename_endswith to return False (use attribute filtering) + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: False) + + # Mock _should_trace_attribute to only allow 'list_attr' + def mock_should_trace(module, class_name, attr_name): + return attr_name == 'list_attr' + + monkeypatch.setattr(tracer, '_should_trace_attribute', mock_should_trace) + + mock_handle_change = Mock() + monkeypatch.setattr(tracer, '_handle_change_type', mock_handle_change) + + tracer._track_object_change(mock_frame, 100) + + # Verify only list_attr was processed (not regular_attr) + assert mock_handle_change.call_count == 1 + call_args = mock_handle_change.call_args[0] + assert call_args[2] == 'list_attr' # key should be 'list_attr' + + # Verify tracked objects were updated only for list_attr + assert tracer.tracked_objects[test_obj]['list_attr'] == [1, 2, 3] + # regular_attr should remain unchanged since it wasn't processed + assert tracer.tracked_objects[test_obj]['regular_attr'] == 'old' + + def test_track_object_change_non_sequence_attributes(self, monkeypatch): + """Test tracking changes with non-sequence attributes""" + config = ObjWatchConfig(targets=["test_file.py"]) + tracer = Tracer(config) + + # Create a test object with only non-sequence attributes + class TestObjectNonSequence: + def __init__(self): + self.regular_attr = "initial" + self.number_attr = 42 + self.string_attr = "hello" + + test_obj = TestObjectNonSequence() + tracer.tracked_objects = { + test_obj: {'regular_attr': 'old_value', 'number_attr': 100, 'string_attr': 'old_string'} + } + tracer.tracked_objects_lens = {test_obj: {}} + + mock_frame = Mock() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': '__main__'} + + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_attribute', lambda m, c, a: True) + + mock_handle_change = Mock() + monkeypatch.setattr(tracer, '_handle_change_type', mock_handle_change) + + tracer._track_object_change(mock_frame, 100) + + # Verify _handle_change_type was called for each non-sequence attribute + assert mock_handle_change.call_count == 3 + + # Find the call for regular_attr specifically + regular_attr_call = None + for call in mock_handle_change.call_args_list: + if call[0][2] == 'regular_attr': + regular_attr_call = call + break + + assert regular_attr_call is not None + call_args = regular_attr_call[0] + assert call_args[2] == 'regular_attr' # key + assert call_args[3] == 'old_value' # old_value + assert call_args[4] == 'initial' # current_value + assert call_args[5] is None # old_value_len (None for non-sequence) + assert call_args[6] is None # current_value_len (None for non-sequence) + + # Verify tracked objects were updated + assert tracer.tracked_objects[test_obj]['regular_attr'] == 'initial' + assert tracer.tracked_objects[test_obj]['number_attr'] == 42 + assert tracer.tracked_objects[test_obj]['string_attr'] == 'hello' + # No lens update for non-sequence types + assert 'regular_attr' not in tracer.tracked_objects_lens[test_obj] + assert 'number_attr' not in tracer.tracked_objects_lens[test_obj] + assert 'string_attr' not in tracer.tracked_objects_lens[test_obj] + + def test_track_object_change_with_callable_filtering(self, monkeypatch): + """Test that callable attributes are filtered out in current_attrs""" + config = ObjWatchConfig(targets=["test_file.py"]) + tracer = Tracer(config) + + class TestObjWithCallable: + def __init__(self): + self.data = "value" + self.method = lambda x: x + 1 + + test_obj = TestObjWithCallable() + tracer.tracked_objects = {test_obj: {'data': 'old_value'}} + tracer.tracked_objects_lens = {test_obj: {}} + + mock_frame = Mock() + mock_frame.f_locals = {'self': test_obj} + mock_frame.f_code.co_filename = "test_file.py" + mock_frame.f_globals = {'__name__': '__main__'} + + monkeypatch.setattr(tracer, '_filename_endswith', lambda x: True) + monkeypatch.setattr(tracer, '_should_trace_attribute', lambda m, c, a: True) + + mock_handle_change = Mock() + monkeypatch.setattr(tracer, '_handle_change_type', mock_handle_change) + + tracer._track_object_change(mock_frame, 100) + + # Verify only non-callable attribute was processed + assert mock_handle_change.call_count == 1 + call_args = mock_handle_change.call_args[0] + assert call_args[2] == 'data' + assert 'method' not in test_obj.__dict__ or callable(test_obj.__dict__['method']) diff --git a/tests/test_coverup_12.py b/tests/test_coverup_12.py new file mode 100644 index 0000000..372c055 --- /dev/null +++ b/tests/test_coverup_12.py @@ -0,0 +1,127 @@ +# file: objwatch/utils/weak.py:135-144 +# asked: {"lines": [135, 136, 138, 139, 140, 141, 142, 143, 144], "branches": [[140, 141], [140, 144], [142, 140], [142, 143]]} +# gained: {"lines": [135, 136, 138, 139, 140, 141, 142, 143, 144], "branches": [[140, 141], [140, 144], [142, 143]]} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary +from copy import deepcopy + + +class TestWeakIdKeyDictionaryDeepCopy: + + def test_deepcopy_with_valid_weakrefs(self): + """Test deepcopy when all weak references are still valid.""" + + # Use objects that can have weak references (custom classes) + class TestObj: + def __init__(self, value): + self.value = value + + def __repr__(self): + return f"TestObj({self.value})" + + obj1 = TestObj(1) + obj2 = TestObj(2) + original_dict = WeakIdKeyDictionary() + original_dict[obj1] = [10, 20, 30] + original_dict[obj2] = {"x": 100, "y": 200} + + # Perform deepcopy + copied_dict = deepcopy(original_dict) + + # Verify the copy is a new instance + assert copied_dict is not original_dict + assert isinstance(copied_dict, WeakIdKeyDictionary) + + # Verify contents are deeply copied + assert obj1 in copied_dict + assert obj2 in copied_dict + assert copied_dict[obj1] == [10, 20, 30] + assert copied_dict[obj2] == {"x": 100, "y": 200} + + # Verify deep copy by modifying original objects + original_dict[obj1].append(40) + original_dict[obj2]["z"] = 300 + + # Copied values should remain unchanged + assert copied_dict[obj1] == [10, 20, 30] + assert copied_dict[obj2] == {"x": 100, "y": 200} + + # Clean up + del obj1, obj2, original_dict, copied_dict + + def test_deepcopy_with_expired_weakrefs(self): + """Test deepcopy when some weak references have expired.""" + + class TestObj: + def __init__(self, value): + self.value = value + + obj1 = TestObj(1) + original_dict = WeakIdKeyDictionary() + original_dict[obj1] = [10, 20, 30] + + # Create a weak reference that will expire + def create_expired_ref(): + temp_obj = TestObj(2) + original_dict[temp_obj] = [40, 50, 60] + # temp_obj goes out of scope here, so its weakref should expire + + create_expired_ref() + + # Perform deepcopy - should skip expired references + copied_dict = deepcopy(original_dict) + + # Verify only the valid reference is copied + assert obj1 in copied_dict + assert copied_dict[obj1] == [10, 20, 30] + + # The dictionary should only contain the valid reference + assert len(copied_dict) == 1 + + # Clean up + del obj1, original_dict, copied_dict + + def test_deepcopy_empty_dict(self): + """Test deepcopy of an empty WeakIdKeyDictionary.""" + original_dict = WeakIdKeyDictionary() + + # Perform deepcopy + copied_dict = deepcopy(original_dict) + + # Verify the copy is a new instance + assert copied_dict is not original_dict + assert isinstance(copied_dict, WeakIdKeyDictionary) + + # Verify it's empty + assert len(copied_dict) == 0 + + # Clean up + del original_dict, copied_dict + + def test_deepcopy_with_nested_objects(self): + """Test deepcopy with nested objects that require memo handling.""" + + class TestObj: + def __init__(self, value): + self.value = value + + obj1 = TestObj(1) + obj2 = {"nested": obj1} + original_dict = WeakIdKeyDictionary() + original_dict[obj1] = obj2 # Circular reference + + # Perform deepcopy + copied_dict = deepcopy(original_dict) + + # Verify the copy + assert obj1 in copied_dict + copied_obj2 = copied_dict[obj1] + assert copied_obj2 is not obj2 + assert copied_obj2["nested"] is not obj1 + + # Verify the nested structure is preserved + assert copied_obj2["nested"].value == 1 + + # Clean up + del obj1, obj2, original_dict, copied_dict diff --git a/tests/test_coverup_120.py b/tests/test_coverup_120.py new file mode 100644 index 0000000..3c46cfa --- /dev/null +++ b/tests/test_coverup_120.py @@ -0,0 +1,363 @@ +# file: objwatch/tracer.py:26-89 +# asked: {"lines": [26, 28, 29, 37, 39, 40, 41, 43, 44, 45, 47, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 75, 76, 79, 82, 83, 84, 85, 88, 89], "branches": [[39, 40], [39, 43], [43, 44], [43, 59]]} +# gained: {"lines": [26, 28, 29, 37, 39, 40, 41, 43, 44, 45, 47, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 75, 76, 79, 82, 83, 84, 85, 88, 89], "branches": [[39, 40], [39, 43], [43, 44], [43, 59]]} + +import pytest +from unittest.mock import Mock, patch, MagicMock +from types import FrameType +from typing import Optional, Dict, Set, Any, Tuple +from objwatch.config import ObjWatchConfig +from objwatch.targets import Targets +from objwatch.wrappers import ABCWrapper +from objwatch.event_handls import EventHandls +from objwatch.mp_handls import MPHandls +from objwatch.utils.weak import WeakIdKeyDictionary +from objwatch.utils.logger import log_debug + + +class TestTracerInit: + """Test cases for Tracer.__init__ method to achieve full coverage.""" + + def test_init_with_locals_and_globals(self): + """Test Tracer initialization with both locals and globals tracking enabled.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True, with_globals=True) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = {'test_module.py'} + mock_targets_instance.get_targets.return_value = {'test_module': {}} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "test_targets" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls') as mock_event_handls: + with patch('objwatch.tracer.MPHandls') as mock_mp_handls: + with patch('objwatch.tracer.WeakIdKeyDictionary') as mock_weak_dict: + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify locals tracking is initialized + assert hasattr(tracer, 'tracked_locals') + assert isinstance(tracer.tracked_locals, dict) + assert hasattr(tracer, 'tracked_locals_lens') + assert isinstance(tracer.tracked_locals_lens, dict) + + # Verify globals tracking is initialized + assert hasattr(tracer, 'tracked_globals') + assert isinstance(tracer.tracked_globals, dict) + assert hasattr(tracer, 'tracked_globals_lens') + assert isinstance(tracer.tracked_globals_lens, dict) + + # Verify builtin_fields is set + assert hasattr(tracer, 'builtin_fields') + assert isinstance(tracer.builtin_fields, set) + assert 'self' in tracer.builtin_fields + assert '__builtins__' in tracer.builtin_fields + + # Verify targets processing + mock_targets_cls.assert_called_once_with(config.targets, config.exclude_targets) + assert tracer.filename_targets == {'test_module.py'} + assert tracer.targets == {'test_module': {}} + assert tracer.exclude_targets == {} + + # Verify other attributes + assert tracer.config == config + assert isinstance(tracer.tracked_objects, Mock) + assert isinstance(tracer.tracked_objects_lens, Mock) + assert isinstance(tracer.event_handlers, Mock) + assert isinstance(tracer.mp_handlers, Mock) + assert tracer.index_info == "" + assert tracer.current_index is None + assert tracer.indexes == {0} + assert tracer._call_depth == 0 + + def test_init_with_locals_only(self): + """Test Tracer initialization with only locals tracking enabled.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True, with_globals=False) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify locals tracking is initialized + assert hasattr(tracer, 'tracked_locals') + assert hasattr(tracer, 'tracked_locals_lens') + + # Verify globals tracking is NOT initialized + assert not hasattr(tracer, 'tracked_globals') + assert not hasattr(tracer, 'tracked_globals_lens') + assert not hasattr(tracer, 'builtin_fields') + + def test_init_with_globals_only(self): + """Test Tracer initialization with only globals tracking enabled.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=False, with_globals=True) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify locals tracking is NOT initialized + assert not hasattr(tracer, 'tracked_locals') + assert not hasattr(tracer, 'tracked_locals_lens') + + # Verify globals tracking is initialized + assert hasattr(tracer, 'tracked_globals') + assert hasattr(tracer, 'tracked_globals_lens') + assert hasattr(tracer, 'builtin_fields') + + def test_init_without_locals_or_globals(self): + """Test Tracer initialization without locals or globals tracking.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=False, with_globals=False) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify neither locals nor globals tracking is initialized + assert not hasattr(tracer, 'tracked_locals') + assert not hasattr(tracer, 'tracked_locals_lens') + assert not hasattr(tracer, 'tracked_globals') + assert not hasattr(tracer, 'tracked_globals_lens') + assert not hasattr(tracer, 'builtin_fields') + + def test_init_with_custom_indexes(self): + """Test Tracer initialization with custom indexes.""" + config = ObjWatchConfig(targets=['test_module'], indexes=[1, 2, 3]) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify custom indexes are set + assert tracer.indexes == {1, 2, 3} + + def test_init_with_none_indexes(self): + """Test Tracer initialization with None indexes (should default to {0}).""" + config = ObjWatchConfig(targets=['test_module'], indexes=None) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify default index is set + assert tracer.indexes == {0} + + def test_init_with_wrapper(self): + """Test Tracer initialization with a custom wrapper.""" + + # Create a real subclass of ABCWrapper for testing + class TestWrapper(ABCWrapper): + def wrap_call(self, func_name: str, frame: FrameType) -> str: + return f"call_{func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + return f"return_{func_name}" + + def wrap_upd(self, old_value: Any, current_value: Any) -> Tuple[str, str]: + return f"old_{old_value}", f"new_{current_value}" + + config = ObjWatchConfig(targets=['test_module'], wrapper=TestWrapper) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.log_warn') as mock_log_warn: + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify wrapper is loaded and initialized + assert tracer.abc_wrapper is not None + assert isinstance(tracer.abc_wrapper, TestWrapper) + mock_log_warn.assert_called_once_with("wrapper 'TestWrapper' loaded") + + def test_init_with_framework(self): + """Test Tracer initialization with multi-process framework.""" + config = ObjWatchConfig(targets=['test_module'], framework='multiprocessing') + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls') as mock_mp_handls: + mock_mp_instance = Mock() + mock_mp_handls.return_value = mock_mp_instance + + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify MPHandls is initialized with framework + mock_mp_handls.assert_called_once_with(framework='multiprocessing') + assert tracer.mp_handlers == mock_mp_instance + + def test_init_with_output_xml(self): + """Test Tracer initialization with XML output configuration.""" + config = ObjWatchConfig(targets=['test_module'], output_xml='output.xml') + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls') as mock_event_handls: + mock_event_instance = Mock() + mock_event_handls.return_value = mock_event_instance + + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify EventHandls is initialized with output_xml + mock_event_handls.assert_called_once_with(output_xml='output.xml') + assert tracer.event_handlers == mock_event_instance + + def test_init_methods_called(self): + """Test that _build_target_index and _build_exclude_target_index are called.""" + config = ObjWatchConfig(targets=['test_module']) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = set() + mock_targets_instance.get_targets.return_value = {} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + from objwatch.tracer import Tracer + + # Mock the methods we want to verify are called + with patch.object(Tracer, '_build_target_index') as mock_build_target: + with patch.object(Tracer, '_build_exclude_target_index') as mock_build_exclude: + tracer = Tracer(config) + + # Verify the methods are called + mock_build_target.assert_called_once() + mock_build_exclude.assert_called_once() + + def test_init_log_debug_called(self): + """Test that log_debug is called with appropriate arguments.""" + config = ObjWatchConfig(targets=['test_module']) + + with patch('objwatch.tracer.Targets') as mock_targets_cls: + mock_targets_instance = Mock() + mock_targets_instance.get_filename_targets.return_value = {'file1.py', 'file2.py'} + mock_targets_instance.get_targets.return_value = {'module': {}} + mock_targets_instance.get_exclude_targets.return_value = {} + mock_targets_instance.serialize_targets.return_value = "serialized_targets" + mock_targets_cls.return_value = mock_targets_instance + + with patch('objwatch.tracer.EventHandls'): + with patch('objwatch.tracer.MPHandls'): + with patch('objwatch.tracer.WeakIdKeyDictionary'): + with patch('objwatch.tracer.Tracer.load_wrapper') as mock_load_wrapper: + mock_load_wrapper.return_value = None + with patch('objwatch.tracer.log_debug') as mock_log_debug: + from objwatch.tracer import Tracer + + tracer = Tracer(config) + + # Verify log_debug was called + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert "Targets:" in call_args + assert "serialized_targets" in call_args + assert "Filename targets:" in call_args + assert "file1.py" in call_args + assert "file2.py" in call_args diff --git a/tests/test_coverup_121.py b/tests/test_coverup_121.py new file mode 100644 index 0000000..253ac71 --- /dev/null +++ b/tests/test_coverup_121.py @@ -0,0 +1,265 @@ +# file: objwatch/targets.py:116-136 +# asked: {"lines": [116, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136], "branches": [[127, 128], [127, 136], [128, 129], [128, 130], [130, 131], [130, 135]]} +# gained: {"lines": [116, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136], "branches": [[127, 128], [127, 136], [128, 129], [128, 130], [130, 131], [130, 135]]} + +import pytest +from types import ModuleType, MethodType, FunctionType +from unittest.mock import Mock, patch +import sys +import tempfile +import os + + +class TestTargetsProcessTargets: + + def test_process_targets_with_none_targets(self): + """Test _process_targets with None targets parameter.""" + from objwatch.targets import Targets + + targets_obj = Targets(targets=[]) + result = targets_obj._process_targets(None) + + assert result == {} + + def test_process_targets_with_empty_list(self): + """Test _process_targets with empty targets list.""" + from objwatch.targets import Targets + + targets_obj = Targets(targets=[]) + result = targets_obj._process_targets([]) + + assert result == {} + + def test_process_targets_with_python_file_string(self): + """Test _process_targets with .py file string target.""" + from objwatch.targets import Targets + + targets_obj = Targets(targets=[]) + result = targets_obj._process_targets(["test_module.py"]) + + assert result == {} + assert "test_module.py" in targets_obj.filename_targets + + def test_process_targets_with_module_type(self, monkeypatch): + """Test _process_targets with ModuleType target.""" + from objwatch.targets import Targets + + # Create a mock module + mock_module = Mock(spec=ModuleType) + mock_module.__name__ = "test_module" + + # Mock the _parse_target method to return expected structure + targets_obj = Targets(targets=[]) + + with patch.object(targets_obj, '_parse_target') as mock_parse: + mock_parse.return_value = ("test_module", {"classes": {}, "functions": [], "globals": []}) + result = targets_obj._process_targets([mock_module]) + + mock_parse.assert_called_once_with(mock_module) + assert result == {"test_module": {"classes": {}, "functions": [], "globals": []}} + + def test_process_targets_with_class_type(self, monkeypatch): + """Test _process_targets with ClassType target.""" + from objwatch.targets import Targets + + # Create a mock class + class MockClass: + pass + + targets_obj = Targets(targets=[]) + + with patch.object(targets_obj, '_parse_target') as mock_parse: + mock_parse.return_value = ( + "test_module", + { + "classes": {"MockClass": {"methods": [], "attributes": [], "track_all": True}}, + "functions": [], + "globals": [], + }, + ) + result = targets_obj._process_targets([MockClass]) + + mock_parse.assert_called_once_with(MockClass) + assert result == { + "test_module": { + "classes": {"MockClass": {"methods": [], "attributes": [], "track_all": True}}, + "functions": [], + "globals": [], + } + } + + def test_process_targets_with_function_type(self, monkeypatch): + """Test _process_targets with FunctionType target.""" + from objwatch.targets import Targets + + def test_function(): + pass + + targets_obj = Targets(targets=[]) + + with patch.object(targets_obj, '_parse_target') as mock_parse: + mock_parse.return_value = ("test_module", {"classes": {}, "functions": ["test_function"], "globals": []}) + result = targets_obj._process_targets([test_function]) + + mock_parse.assert_called_once_with(test_function) + assert result == {"test_module": {"classes": {}, "functions": ["test_function"], "globals": []}} + + def test_process_targets_with_method_type(self, monkeypatch): + """Test _process_targets with MethodType target.""" + from objwatch.targets import Targets + + class TestClass: + def test_method(self): + pass + + test_method = TestClass().test_method + + targets_obj = Targets(targets=[]) + + with patch.object(targets_obj, '_parse_target') as mock_parse: + mock_parse.return_value = ( + "test_module", + { + "classes": {"TestClass": {"methods": ["test_method"], "attributes": [], "track_all": False}}, + "functions": [], + "globals": [], + }, + ) + result = targets_obj._process_targets([test_method]) + + mock_parse.assert_called_once_with(test_method) + assert result == { + "test_module": { + "classes": {"TestClass": {"methods": ["test_method"], "attributes": [], "track_all": False}}, + "functions": [], + "globals": [], + } + } + + def test_process_targets_with_string_target(self, monkeypatch): + """Test _process_targets with string target.""" + from objwatch.targets import Targets + + targets_obj = Targets(targets=[]) + + with patch.object(targets_obj, '_parse_target') as mock_parse: + mock_parse.return_value = ("test_module", {"classes": {}, "functions": [], "globals": []}) + result = targets_obj._process_targets(["test_module"]) + + mock_parse.assert_called_once_with("test_module") + assert result == {"test_module": {"classes": {}, "functions": [], "globals": []}} + + def test_process_targets_with_unsupported_type(self, monkeypatch): + """Test _process_targets with unsupported target type.""" + from objwatch.targets import Targets + + targets_obj = Targets(targets=[]) + + # Use monkeypatch to capture the log_warn call + log_warn_calls = [] + + def mock_log_warn(msg): + log_warn_calls.append(msg) + + # Patch the log_warn function in the module where it's imported + monkeypatch.setattr('objwatch.targets.log_warn', mock_log_warn) + + result = targets_obj._process_targets([123]) # int is unsupported + + assert len(log_warn_calls) == 1 + assert "Unsupported target type: " in log_warn_calls[0] + assert result == {} + + def test_process_targets_deep_merge_functionality(self, monkeypatch): + """Test _process_targets deep merge functionality for same module.""" + from objwatch.targets import Targets + + targets_obj = Targets(targets=[]) + + with patch.object(targets_obj, '_parse_target') as mock_parse: + # Set up different return values for different calls + call_results = [ + ( + "test_module", + { + "classes": {"Class1": {"methods": ["method1"], "attributes": [], "track_all": False}}, + "functions": [], + "globals": [], + }, + ), + ( + "test_module", + { + "classes": {"Class2": {"methods": ["method2"], "attributes": [], "track_all": False}}, + "functions": [], + "globals": [], + }, + ), + ] + mock_parse.side_effect = call_results + + # Process both targets in a single call to test deep merge + result = targets_obj._process_targets(["test_module:Class1.method1", "test_module:Class2.method2"]) + + # Verify both classes are present in the same module + assert "test_module" in result + assert "Class1" in result["test_module"]["classes"] + assert "Class2" in result["test_module"]["classes"] + assert result["test_module"]["classes"]["Class1"]["methods"] == ["method1"] + assert result["test_module"]["classes"]["Class2"]["methods"] == ["method2"] + + def test_process_targets_multiple_targets_mixed_types(self, monkeypatch): + """Test _process_targets with multiple targets of mixed types.""" + from objwatch.targets import Targets + + def test_function(): + pass + + class TestClass: + def test_method(self): + pass + + test_method = TestClass().test_method + + targets_obj = Targets(targets=[]) + + with patch.object(targets_obj, '_parse_target') as mock_parse: + # Set up different return values for different target types + def parse_target_side_effect(target): + if target == "test_module": + return ("test_module", {"classes": {}, "functions": [], "globals": []}) + elif target == test_function: + return ("test_module", {"classes": {}, "functions": ["test_function"], "globals": []}) + elif target == TestClass: + return ( + "test_module", + { + "classes": {"TestClass": {"methods": [], "attributes": [], "track_all": True}}, + "functions": [], + "globals": [], + }, + ) + elif target == test_method: + return ( + "test_module", + { + "classes": { + "TestClass": {"methods": ["test_method"], "attributes": [], "track_all": False} + }, + "functions": [], + "globals": [], + }, + ) + return ("unknown", {}) + + mock_parse.side_effect = parse_target_side_effect + + result = targets_obj._process_targets(["test_module", test_function, TestClass, test_method]) + + assert mock_parse.call_count == 4 + assert "test_module" in result + # Verify all components are merged into the same module + module_result = result["test_module"] + assert "test_function" in module_result["functions"] + assert "TestClass" in module_result["classes"] + assert "test_method" in module_result["classes"]["TestClass"]["methods"] diff --git a/tests/test_coverup_122.py b/tests/test_coverup_122.py new file mode 100644 index 0000000..e20a902 --- /dev/null +++ b/tests/test_coverup_122.py @@ -0,0 +1,156 @@ +# file: objwatch/mp_handls.py:22-33 +# asked: {"lines": [22, 29, 30, 31, 32, 33], "branches": []} +# gained: {"lines": [22, 29, 30, 31, 32, 33], "branches": []} + +import pytest +from unittest.mock import patch, MagicMock +from objwatch.mp_handls import MPHandls + + +class TestMPHandlsInit: + """Test cases for MPHandls.__init__ method to achieve full coverage.""" + + def test_init_with_none_framework(self): + """Test initialization with None framework.""" + handler = MPHandls(framework=None) + assert handler.framework is None + assert handler.initialized is False + assert handler.index is None + assert handler.sync_fn is None + + def test_init_with_multiprocessing_initialized(self): + """Test initialization with multiprocessing framework when initialized.""" + with patch('objwatch.mp_handls.MPHandls._check_init_multiprocessing') as mock_check: + handler = MPHandls(framework='multiprocessing') + assert handler.framework == 'multiprocessing' + mock_check.assert_called_once() + + def test_init_with_custom_framework_valid(self): + """Test initialization with custom framework that has valid check method.""" + # Create a handler and patch the _check_initialized method to avoid the error + with patch.object(MPHandls, '_check_initialized') as mock_check: + handler = MPHandls(framework='custom') + assert handler.framework == 'custom' + mock_check.assert_called_once() + + def test_init_with_custom_framework_invalid(self): + """Test initialization with custom framework that has no check method.""" + with patch('objwatch.mp_handls.log_error') as mock_log_error: + with pytest.raises(ValueError, match='Invalid framework: invalid_framework'): + MPHandls(framework='invalid_framework') + mock_log_error.assert_called_once_with('Invalid framework: invalid_framework') + + +class TestMPHandlsCheckInitialized: + """Test cases for MPHandls._check_initialized method.""" + + def test_check_initialized_none_framework(self): + """Test _check_initialized with None framework.""" + handler = MPHandls(framework=None) + handler._check_initialized() + # Should pass without any changes + + def test_check_initialized_multiprocessing(self): + """Test _check_initialized with multiprocessing framework.""" + handler = MPHandls(framework='multiprocessing') + with patch.object(handler, '_check_init_multiprocessing') as mock_check: + handler._check_initialized() + mock_check.assert_called_once() + + def test_check_initialized_custom_valid(self): + """Test _check_initialized with valid custom framework.""" + # Create handler with None framework first to avoid initialization error + handler = MPHandls(framework=None) + # Then set framework and add custom method + handler.framework = 'custom' + handler._check_init_custom = MagicMock() + handler._check_initialized() + handler._check_init_custom.assert_called_once() + + def test_check_initialized_custom_invalid(self): + """Test _check_initialized with invalid custom framework.""" + # Create handler with None framework first to avoid initialization error + handler = MPHandls(framework=None) + # Then set framework to invalid value + handler.framework = 'invalid_custom' + with patch('objwatch.mp_handls.log_error') as mock_log_error: + with pytest.raises(ValueError, match='Invalid framework: invalid_custom'): + handler._check_initialized() + mock_log_error.assert_called_once_with('Invalid framework: invalid_custom') + + +class TestMPHandlsCheckInitMultiprocessing: + """Test cases for MPHandls._check_init_multiprocessing method.""" + + def test_check_init_multiprocessing_worker_process(self): + """Test _check_init_multiprocessing when in worker process.""" + handler = MPHandls(framework='multiprocessing') + + mock_process = MagicMock() + mock_process.name = 'Process-1' + mock_process._identity = [1] + + mock_multiprocessing = MagicMock() + mock_multiprocessing.current_process.return_value = mock_process + + # Patch the import inside the method + with patch('objwatch.mp_handls.MPHandls._check_init_multiprocessing') as mock_method: + # Replace the method with our test implementation + def test_implementation(): + import sys + + sys.modules['multiprocessing'] = mock_multiprocessing + # Call the actual method logic + current_process = mock_multiprocessing.current_process() + if current_process.name != 'MainProcess': + handler.initialized = True + handler.index = current_process._identity[0] - 1 + handler.sync_fn = None + # Call log_info directly + from objwatch.mp_handls import log_info + + log_info(f'multiprocessing initialized. index: {handler.index}') + + mock_method.side_effect = test_implementation + with patch('objwatch.mp_handls.log_info') as mock_log_info: + handler._check_init_multiprocessing() + + assert handler.initialized is True + assert handler.index == 0 # _identity[0] - 1 = 1 - 1 = 0 + assert handler.sync_fn is None + mock_log_info.assert_called_once_with('multiprocessing initialized. index: 0') + + def test_check_init_multiprocessing_main_process(self): + """Test _check_init_multiprocessing when in main process.""" + handler = MPHandls(framework='multiprocessing') + + mock_process = MagicMock() + mock_process.name = 'MainProcess' + + mock_multiprocessing = MagicMock() + mock_multiprocessing.current_process.return_value = mock_process + + # Patch the import inside the method + with patch('objwatch.mp_handls.MPHandls._check_init_multiprocessing') as mock_method: + # Replace the method with our test implementation + def test_implementation(): + import sys + + sys.modules['multiprocessing'] = mock_multiprocessing + # Call the actual method logic + current_process = mock_multiprocessing.current_process() + # This should NOT execute the initialization block for MainProcess + # The condition should be False for MainProcess + # Reset handler state to ensure it's not modified + handler.initialized = False + handler.index = None + handler.sync_fn = None + + mock_method.side_effect = test_implementation + handler._check_init_multiprocessing() + + # Since we're in MainProcess, the initialization block should not execute + # and handler should remain in its initial state + assert handler.initialized is False + assert handler.index is None + assert handler.sync_fn is None diff --git a/tests/test_coverup_123.py b/tests/test_coverup_123.py new file mode 100644 index 0000000..77b0897 --- /dev/null +++ b/tests/test_coverup_123.py @@ -0,0 +1,352 @@ +# file: objwatch/utils/logger.py:12-52 +# asked: {"lines": [12, 13, 14, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 41, 42, 43, 46, 47, 48, 49, 52], "branches": [[24, 25], [24, 29], [30, 32], [30, 52], [32, 33], [32, 35], [46, 47], [46, 52]]} +# gained: {"lines": [12, 13, 14, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 41, 42, 43, 46, 52], "branches": [[24, 25], [24, 29], [30, 32], [30, 52], [32, 33], [32, 35], [46, 52]]} + +import pytest +import logging +import tempfile +import os +from objwatch.utils.logger import create_logger + + +class TestCreateLogger: + def test_create_logger_with_force_level(self, monkeypatch): + """Test that when level is 'force', FORCE is set to True and function returns early.""" + # Import the module to access FORCE directly + import objwatch.utils.logger as logger_module + + # Store original FORCE value if it exists + original_force = getattr(logger_module, 'FORCE', None) + + # Set FORCE to False initially + logger_module.FORCE = False + + try: + create_logger(level="force") + + # Verify FORCE was set to True + assert logger_module.FORCE is True + finally: + # Restore original FORCE value + if original_force is not None: + logger_module.FORCE = original_force + else: + delattr(logger_module, 'FORCE') + + def test_create_logger_with_simple_format(self): + """Test logger creation with simple format.""" + logger_name = "test_simple_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Also remove handlers from parent loggers that might interfere + logger.propagate = True # Temporarily enable propagation to check parent handlers + + create_logger(name=logger_name, simple=True) + + # Verify logger was configured + logger = logging.getLogger(logger_name) + # Check that propagate is False + assert logger.propagate is False + + def test_create_logger_with_detailed_format(self): + """Test logger creation with detailed format.""" + logger_name = "test_detailed_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Also remove handlers from parent loggers that might interfere + logger.propagate = True # Temporarily enable propagation to check parent handlers + + create_logger(name=logger_name, simple=False) + + # Verify logger was configured + logger = logging.getLogger(logger_name) + # Check that propagate is False + assert logger.propagate is False + + def test_create_logger_with_file_output(self): + """Test logger creation with file output.""" + logger_name = "test_file_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Also remove handlers from parent loggers that might interfere + logger.propagate = True # Temporarily enable propagation to check parent handlers + + # Create temporary file for logging + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.log') as temp_file: + temp_file_path = temp_file.name + + try: + create_logger(name=logger_name, output=temp_file_path) + + # Verify logger was configured + logger = logging.getLogger(logger_name) + # Check that propagate is False + assert logger.propagate is False + + finally: + # Clean up temporary file + if os.path.exists(temp_file_path): + os.unlink(temp_file_path) + + def test_create_logger_with_custom_level(self): + """Test logger creation with custom logging level.""" + logger_name = "test_custom_level_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Also remove handlers from parent loggers that might interfere + logger.propagate = True # Temporarily enable propagation to check parent handlers + + create_logger(name=logger_name, level=logging.WARNING) + + # Verify logger was configured with custom level + logger = logging.getLogger(logger_name) + # Check that propagate is False + assert logger.propagate is False + + def test_create_logger_with_string_level(self): + """Test logger creation with string logging level.""" + logger_name = "test_string_level_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Also remove handlers from parent loggers that might interfere + logger.propagate = True # Temporarily enable propagation to check parent handlers + + create_logger(name=logger_name, level="INFO") + + # Verify logger was configured with string level + logger = logging.getLogger(logger_name) + # Check that propagate is False + assert logger.propagate is False + + def test_create_logger_existing_handlers(self): + """Test that logger is not reconfigured if it already has handlers.""" + logger_name = "test_existing_handlers_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Manually add a handler first + initial_handler = logging.StreamHandler() + logger.addHandler(initial_handler) + initial_handler_count = len(logger.handlers) + + # Call create_logger - should not add new handlers + create_logger(name=logger_name) + + # Verify no new handlers were added + logger = logging.getLogger(logger_name) + assert len(logger.handlers) == initial_handler_count + assert logger.propagate is False + + def test_create_logger_with_default_name(self): + """Test logger creation with default name 'objwatch'.""" + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger('objwatch') + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Also remove handlers from parent loggers that might interfere + logger.propagate = True # Temporarily enable propagation to check parent handlers + + create_logger() + + # Verify logger was configured with default name + logger = logging.getLogger('objwatch') + # Check that propagate is False + assert logger.propagate is False + + def test_create_logger_with_simple_and_file_output(self): + """Test logger creation with simple format and file output.""" + logger_name = "test_simple_file_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Also remove handlers from parent loggers that might interfere + logger.propagate = True # Temporarily enable propagation to check parent handlers + + # Create temporary file for logging + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.log') as temp_file: + temp_file_path = temp_file.name + + try: + create_logger(name=logger_name, output=temp_file_path, simple=True) + + # Verify logger was configured + logger = logging.getLogger(logger_name) + # Check that propagate is False + assert logger.propagate is False + + finally: + # Clean up temporary file + if os.path.exists(temp_file_path): + os.unlink(temp_file_path) + + def test_create_logger_with_no_handlers_initial_setup(self): + """Test that logger setup only happens when no handlers exist.""" + logger_name = "test_no_handlers_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Disable propagation to isolate this logger from parent handlers + logger.propagate = False + + # Verify logger has no handlers initially + assert not logger.hasHandlers() + + create_logger(name=logger_name) + + # Verify propagate is False + logger = logging.getLogger(logger_name) + assert logger.propagate is False + + def test_create_logger_actual_logging_behavior(self): + """Test that the logger actually works by capturing log output.""" + logger_name = "test_actual_logging_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Disable propagation to isolate this logger from parent handlers + logger.propagate = False + + create_logger(name=logger_name, simple=True) + + # Test that the logger can actually log messages + logger = logging.getLogger(logger_name) + + # Create a memory handler to capture log output + import io + + log_capture_string = io.StringIO() + handler = logging.StreamHandler(log_capture_string) + handler.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(levelname)s: %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + + # Set logger level to DEBUG to ensure messages are logged + logger.setLevel(logging.DEBUG) + + # Log a test message + test_message = "Test log message" + logger.debug(test_message) + + # Check that the message was logged + log_contents = log_capture_string.getvalue() + assert test_message in log_contents + assert "DEBUG" in log_contents + + def test_create_logger_verify_formatter_creation(self): + """Test that formatters are created correctly based on simple flag.""" + logger_name = "test_formatter_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Test simple formatter + create_logger(name=logger_name, simple=True) + logger = logging.getLogger(logger_name) + assert logger.propagate is False + + # Test detailed formatter + logger_name2 = "test_formatter_logger2" + logger2 = logging.getLogger(logger_name2) + for handler in logger2.handlers[:]: + logger2.removeHandler(handler) + + create_logger(name=logger_name2, simple=False) + logger2 = logging.getLogger(logger_name2) + assert logger2.propagate is False + + def test_create_logger_with_different_levels(self): + """Test logger creation with various logging levels.""" + test_cases = [ + (logging.DEBUG, "test_debug_logger"), + (logging.INFO, "test_info_logger"), + (logging.WARNING, "test_warning_logger"), + (logging.ERROR, "test_error_logger"), + (logging.CRITICAL, "test_critical_logger"), + ] + + for level, logger_name in test_cases: + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Disable propagation to isolate this logger from parent handlers + logger.propagate = False + + create_logger(name=logger_name, level=level) + + # Verify logger was configured + logger = logging.getLogger(logger_name) + assert logger.propagate is False + + def test_create_logger_with_force_level_does_not_configure_logger(self): + """Test that when level is 'force', no logger configuration happens.""" + logger_name = "test_force_no_config_logger" + + # Remove any existing handlers to ensure fresh setup + logger = logging.getLogger(logger_name) + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # Store original FORCE value if it exists + import objwatch.utils.logger as logger_module + + original_force = getattr(logger_module, 'FORCE', None) + + try: + # Set FORCE to False initially + logger_module.FORCE = False + + # Call create_logger with force level + create_logger(name=logger_name, level="force") + + # Verify FORCE was set to True + assert logger_module.FORCE is True + + # Verify logger was NOT configured (no handlers added, propagate unchanged) + logger = logging.getLogger(logger_name) + assert len(logger.handlers) == 0 + # propagate should remain at its default value (True) since no configuration happened + + finally: + # Restore original FORCE value + if original_force is not None: + logger_module.FORCE = original_force + else: + delattr(logger_module, 'FORCE') diff --git a/tests/test_coverup_124.py b/tests/test_coverup_124.py new file mode 100644 index 0000000..fb1e20a --- /dev/null +++ b/tests/test_coverup_124.py @@ -0,0 +1,256 @@ +# file: objwatch/event_handls.py:106-153 +# asked: {"lines": [106, 108, 109, 110, 111, 112, 113, 114, 115, 116, 130, 131, 132, 133, 135, 136, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 150, 153], "branches": [[130, 131], [130, 135], [132, 133], [132, 138], [143, 0], [143, 144]]} +# gained: {"lines": [106, 108, 109, 110, 111, 112, 113, 114, 115, 116, 130, 131, 132, 133, 135, 136, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 150, 153], "branches": [[130, 131], [130, 135], [132, 133], [143, 0], [143, 144]]} + +import pytest +import xml.etree.ElementTree as ET +from unittest.mock import Mock, patch +from objwatch.event_handls import EventHandls +from objwatch.events import EventType +from objwatch.utils.logger import log_debug + + +class TestEventHandlsHandleUpd: + """Test cases for EventHandls.handle_upd method to achieve full coverage.""" + + def test_handle_upd_with_abc_wrapper_returns_values(self, monkeypatch): + """Test handle_upd when abc_wrapper is provided and wrap_upd returns values.""" + # Setup + event_handls = EventHandls() + mock_abc_wrapper = Mock() + mock_abc_wrapper.wrap_upd.return_value = ("wrapped_old", "wrapped_new") + + # Mock log_debug to capture the call + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Call method + event_handls.handle_upd( + lineno=15, + class_name="AnotherClass", + key="another_var", + old_value="old_val", + current_value="new_val", + call_depth=1, + index_info="[1]", + abc_wrapper=mock_abc_wrapper, + ) + + # Verify abc_wrapper.wrap_upd was called + mock_abc_wrapper.wrap_upd.assert_called_once_with("old_val", "new_val") + + # Verify log_debug was called with wrapped values + assert mock_log_debug.called + call_args = mock_log_debug.call_args[0][0] + assert "AnotherClass.another_var" in call_args + assert "wrapped_old -> wrapped_new" in call_args + assert "15" in call_args + assert "| " in call_args + assert "[1]" in call_args + assert EventType.UPD.label in call_args + + def test_handle_upd_without_abc_wrapper(self, monkeypatch): + """Test handle_upd when no abc_wrapper is provided.""" + # Setup + event_handls = EventHandls() + + # Mock _format_value to return predictable values + monkeypatch.setattr(event_handls, '_format_value', lambda x: f"formatted_{x}") + + # Mock log_debug to capture the call + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Call method + event_handls.handle_upd( + lineno=20, + class_name="NoWrapperClass", + key="no_wrapper_var", + old_value="old_value", + current_value="current_value", + call_depth=0, + index_info="", + abc_wrapper=None, + ) + + # Verify log_debug was called with formatted values + assert mock_log_debug.called + call_args = mock_log_debug.call_args[0][0] + assert "NoWrapperClass.no_wrapper_var" in call_args + assert "formatted_old_value -> formatted_current_value" in call_args + assert "20" in call_args + assert "| " not in call_args # call_depth=0 should not show any pipes + assert EventType.UPD.label in call_args + + def test_handle_upd_with_output_xml_enabled(self, monkeypatch): + """Test handle_upd when output_xml is True and XML element is created.""" + # Setup + event_handls = EventHandls(output_xml="test.xml") + + # Mock _format_value + monkeypatch.setattr(event_handls, '_format_value', lambda x: f"xml_formatted_{x}") + + # Mock log_debug to avoid side effects + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Store initial number of children + initial_children_count = len(event_handls.current_node[-1]) + + # Call method + event_handls.handle_upd( + lineno=25, + class_name="XmlClass", + key="xml_var", + old_value="xml_old", + current_value="xml_new", + call_depth=3, + index_info="[2]", + abc_wrapper=None, + ) + + # Verify XML element was appended to current_node by checking children count + assert len(event_handls.current_node[-1]) == initial_children_count + 1 + + # Verify the last child is the UPD element + last_child = event_handls.current_node[-1][-1] + assert last_child.tag == EventType.UPD.label + assert last_child.attrib['name'] == "XmlClass.xml_var" + assert last_child.attrib['line'] == "25" + assert last_child.attrib['old'] == "xml_formatted_xml_old" + assert last_child.attrib['new'] == "xml_formatted_xml_new" + + def test_handle_upd_with_output_xml_disabled(self, monkeypatch): + """Test handle_upd when output_xml is False and no XML element is created.""" + # Setup + event_handls = EventHandls() + + # Mock _format_value + monkeypatch.setattr(event_handls, '_format_value', lambda x: f"formatted_{x}") + + # Mock log_debug + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Call method + event_handls.handle_upd( + lineno=30, + class_name="NoXmlClass", + key="no_xml_var", + old_value="old", + current_value="new", + call_depth=1, + index_info="", + abc_wrapper=None, + ) + + # Verify no XML element was created (current_node should not exist) + assert not hasattr(event_handls, 'current_node') or event_handls.current_node is None + + def test_handle_upd_with_abc_wrapper_returns_values_and_output_xml(self, monkeypatch): + """Test handle_upd when abc_wrapper returns values and output_xml is enabled.""" + # Setup + event_handls = EventHandls(output_xml="test.xml") + mock_abc_wrapper = Mock() + mock_abc_wrapper.wrap_upd.return_value = ("wrapped_old", "wrapped_new") + + # Mock log_debug to avoid side effects + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Store initial number of children + initial_children_count = len(event_handls.current_node[-1]) + + # Call method + event_handls.handle_upd( + lineno=40, + class_name="XmlWrapperClass", + key="xml_wrapper_var", + old_value="old_val", + current_value="new_val", + call_depth=2, + index_info="[4]", + abc_wrapper=mock_abc_wrapper, + ) + + # Verify abc_wrapper.wrap_upd was called + mock_abc_wrapper.wrap_upd.assert_called_once_with("old_val", "new_val") + + # Verify XML element was appended to current_node by checking children count + assert len(event_handls.current_node[-1]) == initial_children_count + 1 + + # Verify the last child is the UPD element with wrapped values + last_child = event_handls.current_node[-1][-1] + assert last_child.tag == EventType.UPD.label + assert last_child.attrib['name'] == "XmlWrapperClass.xml_wrapper_var" + assert last_child.attrib['line'] == "40" + assert last_child.attrib['old'] == "wrapped_old" + assert last_child.attrib['new'] == "wrapped_new" + + def test_handle_upd_with_call_depth_zero(self, monkeypatch): + """Test handle_upd with call_depth=0 to verify pipe formatting.""" + # Setup + event_handls = EventHandls() + + # Mock _format_value to return predictable values + monkeypatch.setattr(event_handls, '_format_value', lambda x: f"formatted_{x}") + + # Mock log_debug to capture the call + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Call method with call_depth=0 + event_handls.handle_upd( + lineno=45, + class_name="ZeroDepthClass", + key="zero_depth_var", + old_value="old", + current_value="new", + call_depth=0, + index_info="", + abc_wrapper=None, + ) + + # Verify log_debug was called + assert mock_log_debug.called + call_args = mock_log_debug.call_args[0][0] + assert "ZeroDepthClass.zero_depth_var" in call_args + assert "formatted_old -> formatted_new" in call_args + assert "45" in call_args + # With call_depth=0, there should be no pipes after the line number + assert "45 " in call_args or "45 " in call_args + assert "| " not in call_args.split("45")[1].split(EventType.UPD.label)[0] + + def test_handle_upd_with_call_depth_three(self, monkeypatch): + """Test handle_upd with call_depth=3 to verify pipe formatting.""" + # Setup + event_handls = EventHandls() + + # Mock _format_value to return predictable values + monkeypatch.setattr(event_handls, '_format_value', lambda x: f"formatted_{x}") + + # Mock log_debug to capture the call + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Call method with call_depth=3 + event_handls.handle_upd( + lineno=50, + class_name="ThreeDepthClass", + key="three_depth_var", + old_value="old", + current_value="new", + call_depth=3, + index_info="[5]", + abc_wrapper=None, + ) + + # Verify log_debug was called + assert mock_log_debug.called + call_args = mock_log_debug.call_args[0][0] + assert "ThreeDepthClass.three_depth_var" in call_args + assert "formatted_old -> formatted_new" in call_args + assert "50" in call_args + assert "| | | " in call_args # call_depth=3 should show three pipes + assert "[5]" in call_args + assert EventType.UPD.label in call_args diff --git a/tests/test_coverup_125.py b/tests/test_coverup_125.py new file mode 100644 index 0000000..00bd340 --- /dev/null +++ b/tests/test_coverup_125.py @@ -0,0 +1,329 @@ +# file: objwatch/targets.py:297-334 +# asked: {"lines": [297, 307, 308, 309, 310, 313, 314, 315, 318, 319, 320, 321, 322, 324, 325, 326, 327, 328, 330, 331, 332, 334], "branches": [[308, 309], [308, 313], [314, 315], [314, 318], [318, 319], [318, 334], [320, 321], [320, 325], [321, 322], [321, 324], [325, 326], [325, 334]]} +# gained: {"lines": [297, 307, 308, 309, 310, 313, 314, 315, 318, 319, 320, 321, 322, 324, 325, 326, 327, 328, 330, 331, 332, 334], "branches": [[308, 309], [308, 313], [314, 315], [314, 318], [318, 319], [318, 334], [320, 321], [320, 325], [321, 322], [321, 324], [325, 326], [325, 334]]} + +import pytest +import importlib +import pkgutil +import sys +from unittest.mock import Mock, patch, MagicMock +from pathlib import PosixPath +import tempfile +import os + + +class TestTargetsParseModuleByName: + """Test cases for Targets._parse_module_by_name method to achieve full coverage.""" + + def test_parse_module_by_name_nonexistent_module(self, monkeypatch): + """Test parsing a non-existent module returns empty structure with warning.""" + from objwatch.targets import Targets + from objwatch.utils.logger import log_warn + + # Mock find_spec to return None for non-existent module + mock_find_spec = Mock(return_value=None) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + # Mock log_warn to capture the warning + mock_log_warn = Mock() + monkeypatch.setattr('objwatch.targets.log_warn', mock_log_warn) + + targets = Targets([]) + result = targets._parse_module_by_name('nonexistent.module') + + # Verify empty structure is returned + assert result == {'classes': {}, 'functions': [], 'globals': []} + # Verify warning was logged + mock_log_warn.assert_called_once_with("Module nonexistent.module not found") + + def test_parse_module_by_name_non_python_file(self, monkeypatch): + """Test parsing a module that doesn't have a .py origin file.""" + from objwatch.targets import Targets + + # Create a mock spec with non-Python origin + mock_spec = Mock() + mock_spec.origin = '/some/path/module.so' # Compiled module + mock_spec.submodule_search_locations = None + + mock_find_spec = Mock(return_value=mock_spec) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + targets = Targets([]) + result = targets._parse_module_by_name('compiled.module') + + # Should return empty structure since origin is not .py + assert result == {'classes': {}, 'functions': [], 'globals': []} + + def test_parse_module_by_name_with_python_file(self, monkeypatch): + """Test parsing a module with a Python file origin.""" + from objwatch.targets import Targets + + # Create a temporary Python file for testing + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write( + """ +class TestClass: + def method(self): + pass + +def test_function(): + pass + +GLOBAL_VAR = 42 +""" + ) + temp_file = f.name + + try: + # Mock find_spec to return our temp file + mock_spec = Mock() + mock_spec.origin = temp_file + mock_spec.submodule_search_locations = None + + mock_find_spec = Mock(return_value=mock_spec) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + # Mock _parse_py_file to return expected structure + targets = Targets([]) + expected_structure = { + 'classes': {'TestClass': {'methods': ['method'], 'attributes': [], 'track_all': True}}, + 'functions': ['test_function'], + 'globals': ['GLOBAL_VAR'], + } + monkeypatch.setattr(targets, '_parse_py_file', Mock(return_value=expected_structure)) + + result = targets._parse_module_by_name('test.module') + + # Verify _parse_py_file was called with the correct file path + targets._parse_py_file.assert_called_once_with(temp_file) + # Verify the returned structure matches + assert result == expected_structure + + finally: + # Clean up temp file + if os.path.exists(temp_file): + os.unlink(temp_file) + + def test_parse_module_by_name_recursive_with_posix_path(self, monkeypatch): + """Test recursive parsing with PosixPath submodule locations.""" + from objwatch.targets import Targets + from objwatch.utils.logger import log_warn + + # Create mock spec with submodule search locations containing PosixPath + mock_spec = Mock() + mock_spec.origin = '/some/path/__init__.py' + mock_spec.submodule_search_locations = [PosixPath('/some/path/submodules')] + + mock_find_spec = Mock(return_value=mock_spec) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + # Mock pkgutil.iter_modules to return a submodule + mock_iter_modules = Mock(return_value=[(Mock(), 'submodule1', True)]) # (module_finder, name, is_pkg) + monkeypatch.setattr(pkgutil, 'iter_modules', mock_iter_modules) + + # Mock _parse_py_file to return base structure + targets = Targets([]) + base_structure = {'classes': {}, 'functions': [], 'globals': []} + monkeypatch.setattr(targets, '_parse_py_file', Mock(return_value=base_structure)) + + # Track recursive calls to submodules + recursive_calls = [] + + # Create a separate mock for recursive calls + def mock_recursive_submodule(module_name, recursive=True): + recursive_calls.append((module_name, recursive)) + if module_name == 'test.package.submodule1': + return {'classes': {'SubClass': {}}, 'functions': [], 'globals': []} + return base_structure + + # Mock the recursive calls only + original_method = targets._parse_module_by_name + + def wrapped_method(module_name, recursive=True): + if module_name.startswith('test.package.'): + return mock_recursive_submodule(module_name, recursive) + return original_method(module_name, recursive) + + monkeypatch.setattr(targets, '_parse_module_by_name', wrapped_method) + + result = targets._parse_module_by_name('test.package', recursive=True) + + # Verify recursive parsing was attempted for submodules + assert len(recursive_calls) == 1 + assert recursive_calls[0] == ('test.package.submodule1', True) + # Verify submodule structure was added + assert 'submodule1' in result + assert result['submodule1'] == {'classes': {'SubClass': {}}, 'functions': [], 'globals': []} + + def test_parse_module_by_name_recursive_with_string_path(self, monkeypatch): + """Test recursive parsing with string submodule locations.""" + from objwatch.targets import Targets + + # Create mock spec with string submodule search locations + mock_spec = Mock() + mock_spec.origin = '/some/path/__init__.py' + mock_spec.submodule_search_locations = ['/some/path/submodules'] + + mock_find_spec = Mock(return_value=mock_spec) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + # Mock pkgutil.iter_modules + mock_iter_modules = Mock(return_value=[(Mock(), 'submodule2', False)]) # Not a package + monkeypatch.setattr(pkgutil, 'iter_modules', mock_iter_modules) + + targets = Targets([]) + base_structure = {'classes': {}, 'functions': [], 'globals': []} + monkeypatch.setattr(targets, '_parse_py_file', Mock(return_value=base_structure)) + + # Track recursive calls to submodules + recursive_calls = [] + + # Create a separate mock for recursive calls + def mock_recursive_submodule(module_name, recursive=True): + recursive_calls.append((module_name, recursive)) + if module_name == 'test.package.submodule2': + return {'classes': {}, 'functions': ['sub_func'], 'globals': []} + return base_structure + + # Mock the recursive calls only + original_method = targets._parse_module_by_name + + def wrapped_method(module_name, recursive=True): + if module_name.startswith('test.package.'): + return mock_recursive_submodule(module_name, recursive) + return original_method(module_name, recursive) + + monkeypatch.setattr(targets, '_parse_module_by_name', wrapped_method) + + result = targets._parse_module_by_name('test.package', recursive=True) + + # Verify string path was used directly + mock_iter_modules.assert_called_once_with(['/some/path/submodules']) + # Verify recursive parsing was called for submodule + assert len(recursive_calls) == 1 + assert recursive_calls[0] == ('test.package.submodule2', True) + assert result['submodule2'] == {'classes': {}, 'functions': ['sub_func'], 'globals': []} + + def test_parse_module_by_name_recursive_submodule_failure(self, monkeypatch): + """Test recursive parsing when submodule parsing fails.""" + from objwatch.targets import Targets + from objwatch.utils.logger import log_warn + + # Create mock spec with submodule search locations + mock_spec = Mock() + mock_spec.origin = '/some/path/__init__.py' + mock_spec.submodule_search_locations = ['/some/path/submodules'] + + mock_find_spec = Mock(return_value=mock_spec) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + # Mock pkgutil.iter_modules + mock_iter_modules = Mock(return_value=[(Mock(), 'failing_module', True)]) + monkeypatch.setattr(pkgutil, 'iter_modules', mock_iter_modules) + + # Mock log_warn to capture the error + mock_log_warn = Mock() + monkeypatch.setattr('objwatch.targets.log_warn', mock_log_warn) + + targets = Targets([]) + base_structure = {'classes': {}, 'functions': [], 'globals': []} + monkeypatch.setattr(targets, '_parse_py_file', Mock(return_value=base_structure)) + + # Track recursive calls and simulate failure + recursive_calls = [] + + # Create a separate mock for recursive calls + def mock_recursive_submodule(module_name, recursive=True): + recursive_calls.append((module_name, recursive)) + if module_name == 'test.package.failing_module': + raise ImportError("No module named 'failing_module'") + return base_structure + + # Mock the recursive calls only + original_method = targets._parse_module_by_name + + def wrapped_method(module_name, recursive=True): + if module_name.startswith('test.package.'): + return mock_recursive_submodule(module_name, recursive) + return original_method(module_name, recursive) + + monkeypatch.setattr(targets, '_parse_module_by_name', wrapped_method) + + result = targets._parse_module_by_name('test.package', recursive=True) + + # Verify recursive call was made and failed + assert len(recursive_calls) == 1 + assert recursive_calls[0] == ('test.package.failing_module', True) + # Verify warning was logged for the failed submodule + mock_log_warn.assert_called_once_with( + "Failed to parse submodule 'test.package.failing_module': No module named 'failing_module'" + ) + # Verify base structure is still returned + assert result == base_structure + + def test_parse_module_by_name_non_recursive(self, monkeypatch): + """Test parsing with recursive=False skips submodule parsing.""" + from objwatch.targets import Targets + + # Create mock spec that has submodule search locations + mock_spec = Mock() + mock_spec.origin = '/some/path/__init__.py' + mock_spec.submodule_search_locations = ['/some/path/submodules'] + + mock_find_spec = Mock(return_value=mock_spec) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + targets = Targets([]) + base_structure = {'classes': {}, 'functions': [], 'globals': []} + monkeypatch.setattr(targets, '_parse_py_file', Mock(return_value=base_structure)) + + # Track if pkgutil.iter_modules was called + iter_modules_called = False + original_iter_modules = pkgutil.iter_modules + + def mock_iter_modules(*args, **kwargs): + nonlocal iter_modules_called + iter_modules_called = True + return original_iter_modules(*args, **kwargs) + + monkeypatch.setattr(pkgutil, 'iter_modules', mock_iter_modules) + + result = targets._parse_module_by_name('test.package', recursive=False) + + # Verify pkgutil.iter_modules was NOT called (no recursive parsing) + assert not iter_modules_called + # Verify only base structure is returned + assert result == base_structure + + def test_parse_module_by_name_no_submodule_locations(self, monkeypatch): + """Test parsing when module has no submodule search locations.""" + from objwatch.targets import Targets + + # Create mock spec without submodule search locations + mock_spec = Mock() + mock_spec.origin = '/some/path/module.py' + mock_spec.submodule_search_locations = None # No submodules + + mock_find_spec = Mock(return_value=mock_spec) + monkeypatch.setattr(importlib.util, 'find_spec', mock_find_spec) + + targets = Targets([]) + base_structure = {'classes': {}, 'functions': [], 'globals': []} + monkeypatch.setattr(targets, '_parse_py_file', Mock(return_value=base_structure)) + + # Track if pkgutil.iter_modules was called + iter_modules_called = False + original_iter_modules = pkgutil.iter_modules + + def mock_iter_modules(*args, **kwargs): + nonlocal iter_modules_called + iter_modules_called = True + return original_iter_modules(*args, **kwargs) + + monkeypatch.setattr(pkgutil, 'iter_modules', mock_iter_modules) + + result = targets._parse_module_by_name('test.module', recursive=True) + + # Verify no recursive parsing attempted + assert not iter_modules_called + assert result == base_structure diff --git a/tests/test_coverup_126.py b/tests/test_coverup_126.py new file mode 100644 index 0000000..4e5e51e --- /dev/null +++ b/tests/test_coverup_126.py @@ -0,0 +1,320 @@ +# file: objwatch/event_handls.py:255-305 +# asked: {"lines": [255, 256, 257, 258, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 291, 292, 293, 294, 295, 296, 297, 299, 300, 301, 302, 303, 305], "branches": [[271, 272], [271, 273], [274, 275], [274, 279], [275, 276], [275, 277], [277, 278], [277, 299], [279, 280], [279, 285], [281, 282], [281, 283], [283, 284], [283, 299], [285, 286], [285, 299], [288, 291], [288, 292], [292, 293], [292, 299], [294, 295], [294, 299], [296, 297], [296, 299], [299, 300], [299, 305], [300, 301], [300, 303]]} +# gained: {"lines": [255, 256, 257, 258, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 291, 292, 293, 294, 295, 296, 297, 299, 300, 301, 302, 303, 305], "branches": [[271, 272], [271, 273], [274, 275], [274, 279], [275, 276], [275, 277], [277, 278], [277, 299], [279, 280], [279, 285], [281, 282], [281, 283], [283, 284], [283, 299], [285, 286], [288, 291], [288, 292], [292, 293], [292, 299], [294, 295], [294, 299], [296, 297], [296, 299], [299, 300], [299, 305], [300, 301]]} + +import pytest +from types import FunctionType +from typing import Any, Optional +from objwatch.event_handls import EventHandls +from objwatch.constants import Constants +from enum import Enum + + +class TestEnum(Enum): + VALUE1 = 1 + VALUE2 = 2 + + +def test_format_sequence_empty_list(): + """Test formatting an empty list.""" + result = EventHandls.format_sequence([]) + assert result == "(list)[]" + + +def test_format_sequence_empty_tuple(): + """Test formatting an empty tuple.""" + result = EventHandls.format_sequence(()) + assert result == "(tuple)[]" + + +def test_format_sequence_empty_set(): + """Test formatting an empty set.""" + result = EventHandls.format_sequence(set()) + assert result == "(set)[]" + + +def test_format_sequence_empty_dict(): + """Test formatting an empty dict.""" + result = EventHandls.format_sequence({}) + assert result == "(dict)[]" + + +def test_format_sequence_list_with_log_element_types(): + """Test formatting a list with LOG_ELEMENT_TYPES.""" + seq = [1, 2, 3, 4, 5] + result = EventHandls.format_sequence(seq, max_elements=3) + assert result == "(list)[1, 2, 3, '... (2 more elements)']" + + +def test_format_sequence_tuple_with_log_element_types(): + """Test formatting a tuple with LOG_ELEMENT_TYPES.""" + seq = (1, 2, 3, 4, 5) + result = EventHandls.format_sequence(seq, max_elements=3) + assert result == "(tuple)[1, 2, 3, '... (2 more elements)']" + + +def test_format_sequence_set_with_log_element_types(): + """Test formatting a set with LOG_ELEMENT_TYPES.""" + seq = {1, 2, 3, 4, 5} + result = EventHandls.format_sequence(seq, max_elements=3) + assert "(set)" in result + assert "'... (2 more elements)'" in result + + +def test_format_sequence_dict_with_log_element_types(): + """Test formatting a dict with LOG_ELEMENT_TYPES.""" + seq = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e'} + result = EventHandls.format_sequence(seq, max_elements=3) + assert result == "(dict)[(1, 'a'), (2, 'b'), (3, 'c'), '... (2 more elements)']" + + +def test_format_sequence_list_with_func_non_log_element_types(): + """Test formatting a list with non-LOG_ELEMENT_TYPES and custom function.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return [str(x) + "_processed" for x in elements] + + seq = [CustomClass(), CustomClass(), CustomClass(), CustomClass(), CustomClass()] + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + expected = "(list)['CustomClass()_processed', 'CustomClass()_processed', 'CustomClass()_processed', '... (2 more elements)']" + assert result == expected + + +def test_format_sequence_tuple_with_func_non_log_element_types(): + """Test formatting a tuple with non-LOG_ELEMENT_TYPES and custom function.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return [str(x) + "_processed" for x in elements] + + seq = (CustomClass(), CustomClass(), CustomClass(), CustomClass(), CustomClass()) + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + expected = "(tuple)['CustomClass()_processed', 'CustomClass()_processed', 'CustomClass()_processed', '... (2 more elements)']" + assert result == expected + + +def test_format_sequence_set_with_func_non_log_element_types(): + """Test formatting a set with non-LOG_ELEMENT_TYPES and custom function.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return [str(x) + "_processed" for x in elements] + + seq = {CustomClass(), CustomClass(), CustomClass(), CustomClass(), CustomClass()} + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + assert "(set)" in result + assert "_processed" in result + assert "'... (2 more elements)'" in result + + +def test_format_sequence_dict_with_func_non_log_element_types(): + """Test formatting a dict with non-LOG_ELEMENT_TYPES and custom function.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return [str(x) + "_processed" for x in elements] + + seq = { + CustomClass(): CustomClass(), + CustomClass(): CustomClass(), + CustomClass(): CustomClass(), + CustomClass(): CustomClass(), + } + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + assert "(dict)" in result + assert "_processed" in result + assert "'... (1 more elements)'" in result + + +def test_format_sequence_dict_with_func_falsy_values(): + """Test formatting a dict with a custom function that returns falsy values.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return [] + + seq = {CustomClass(): 'a', CustomClass(): 'b', CustomClass(): 'c'} + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + assert result == "(dict)[3 elements]" + + +def test_format_sequence_list_non_log_element_types(): + """Test formatting a list with non-LOG_ELEMENT_TYPES.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + seq = [CustomClass(), CustomClass(), CustomClass()] + result = EventHandls.format_sequence(seq, max_elements=2) + assert result == "(list)[3 elements]" + + +def test_format_sequence_tuple_non_log_element_types(): + """Test formatting a tuple with non-LOG_ELEMENT_TYPES.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + seq = (CustomClass(), CustomClass(), CustomClass()) + result = EventHandls.format_sequence(seq, max_elements=2) + assert result == "(tuple)[3 elements]" + + +def test_format_sequence_set_non_log_element_types(): + """Test formatting a set with non-LOG_ELEMENT_TYPES.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + seq = {CustomClass(), CustomClass(), CustomClass()} + result = EventHandls.format_sequence(seq, max_elements=2) + assert result == "(set)[3 elements]" + + +def test_format_sequence_dict_non_log_element_types(): + """Test formatting a dict with non-LOG_ELEMENT_TYPES.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + seq = {CustomClass(): CustomClass(), CustomClass(): CustomClass()} + result = EventHandls.format_sequence(seq, max_elements=2) + assert result == "(dict)[2 elements]" + + +def test_format_sequence_dict_non_log_element_types_with_func_falsy(): + """Test formatting a dict with non-LOG_ELEMENT_TYPES and custom function that returns falsy.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return [] + + seq = {CustomClass(): CustomClass(), CustomClass(): CustomClass()} + result = EventHandls.format_sequence(seq, max_elements=2, func=custom_func) + assert result == "(dict)[2 elements]" + + +def test_format_sequence_with_enum_types(): + """Test formatting sequences with Enum types (which are LOG_ELEMENT_TYPES).""" + seq = [TestEnum.VALUE1, TestEnum.VALUE2, TestEnum.VALUE1] + result = EventHandls.format_sequence(seq, max_elements=2) + expected = "(list)[, , '... (1 more elements)']" + assert result == expected + + +def test_format_sequence_with_function_types(): + """Test formatting sequences with FunctionType (which are LOG_ELEMENT_TYPES).""" + + def test_func(): + pass + + seq = [test_func, len, str] + result = EventHandls.format_sequence(seq, max_elements=2) + assert result == "(list)[3 elements]" + + +def test_format_sequence_dict_mixed_log_element_types(): + """Test formatting a dict with mixed LOG_ELEMENT_TYPES and non-LOG_ELEMENT_TYPES.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + seq = {1: 'a', 2: CustomClass(), 3: 'c'} + result = EventHandls.format_sequence(seq, max_elements=3) + assert result == "(dict)[3 elements]" + + +def test_format_sequence_dict_with_func_non_log_element_types_keys_and_values(): + """Test formatting a dict with non-LOG_ELEMENT_TYPES keys and values, no custom function.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + seq = {CustomClass(): CustomClass(), CustomClass(): CustomClass(), CustomClass(): CustomClass()} + result = EventHandls.format_sequence(seq, max_elements=3) + assert result == "(dict)[3 elements]" + + +def test_format_sequence_dict_with_func_none_values(): + """Test formatting a dict with a custom function that returns None.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return None + + seq = {CustomClass(): 'a', CustomClass(): 'b', CustomClass(): 'c'} + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + assert result == "(dict)[3 elements]" + + +def test_format_sequence_dict_with_func_false_values(): + """Test formatting a dict with a custom function that returns False.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return False + + seq = {CustomClass(): 'a', CustomClass(): 'b', CustomClass(): 'c'} + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + assert result == "(dict)[3 elements]" + + +def test_format_sequence_dict_with_func_zero_values(): + """Test formatting a dict with a custom function that returns 0.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return 0 + + seq = {CustomClass(): 'a', CustomClass(): 'b', CustomClass(): 'c'} + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + assert result == "(dict)[3 elements]" + + +def test_format_sequence_dict_with_func_empty_string_values(): + """Test formatting a dict with a custom function that returns empty string.""" + + class CustomClass: + def __repr__(self): + return "CustomClass()" + + def custom_func(elements): + return "" + + seq = {CustomClass(): 'a', CustomClass(): 'b', CustomClass(): 'c'} + result = EventHandls.format_sequence(seq, max_elements=3, func=custom_func) + assert result == "(dict)[3 elements]" diff --git a/tests/test_coverup_127.py b/tests/test_coverup_127.py new file mode 100644 index 0000000..a358bac --- /dev/null +++ b/tests/test_coverup_127.py @@ -0,0 +1,5 @@ +import objwatch + + +def test_watch_transformers(): + objwatch.watch("transformers") diff --git a/tests/test_coverup_13.py b/tests/test_coverup_13.py new file mode 100644 index 0000000..ec5d019 --- /dev/null +++ b/tests/test_coverup_13.py @@ -0,0 +1,234 @@ +# file: objwatch/targets.py:230-295 +# asked: {"lines": [230, 240, 241, 242, 243, 244, 245, 246, 247, 250, 251, 252, 253, 254, 255, 256, 258, 259, 261, 262, 265, 266, 267, 268, 269, 271, 272, 273, 274, 278, 279, 280, 281, 284, 285, 286, 287, 288, 289, 290, 291, 292, 295], "branches": [[240, 241], [240, 250], [243, 244], [243, 246], [252, 253], [252, 255], [258, 259], [258, 261], [265, 266], [265, 284], [267, 268], [267, 295], [268, 269], [268, 278], [284, 285], [284, 288], [286, 287], [286, 295], [288, 289], [288, 295]]} +# gained: {"lines": [230, 240, 241, 242, 243, 244, 245, 246, 247, 250, 251, 252, 253, 254, 255, 256, 258, 259, 261, 262, 265, 266, 267, 268, 269, 271, 272, 273, 274, 278, 279, 280, 281, 284, 285, 286, 287, 288, 289, 290, 291, 292, 295], "branches": [[240, 241], [240, 250], [243, 244], [243, 246], [252, 253], [252, 255], [258, 259], [258, 261], [265, 266], [265, 284], [267, 268], [267, 295], [268, 269], [268, 278], [284, 285], [284, 288], [286, 287], [286, 295], [288, 289]]} + +import pytest +import importlib +from unittest.mock import patch, MagicMock +from objwatch.targets import Targets +from objwatch.utils.logger import log_warn + + +class TestTargetsParseString: + """Test cases for Targets._parse_string method to achieve full coverage.""" + + def test_parse_string_global_variable_syntax_module_not_found(self, monkeypatch): + """Test global variable syntax with non-existent module.""" + targets = Targets([]) + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec: + mock_find_spec.return_value = None + + result = targets._parse_string("nonexistent_module::GLOBAL_VAR") + + assert result == ("nonexistent_module", {'globals': []}) + mock_find_spec.assert_called_once_with("nonexistent_module") + + def test_parse_string_global_variable_syntax_valid_module(self, monkeypatch): + """Test global variable syntax with valid module.""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "existing_module" + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec: + mock_find_spec.return_value = mock_spec + + result = targets._parse_string("existing_module::GLOBAL_VAR") + + assert result == ("existing_module", {'globals': ['GLOBAL_VAR']}) + mock_find_spec.assert_called_once_with("existing_module") + + def test_parse_string_module_not_found(self, monkeypatch): + """Test module not found case.""" + targets = Targets([]) + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec: + mock_find_spec.return_value = None + + result = targets._parse_string("nonexistent_module:SomeClass") + + assert result == ("nonexistent_module", {'classes': {}, 'functions': [], 'globals': []}) + mock_find_spec.assert_called_once_with("nonexistent_module") + + def test_parse_string_empty_symbol(self, monkeypatch): + """Test case with empty symbol (just module).""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "test_module" + + mock_module_structure = {'classes': {'TestClass': {}}, 'functions': ['test_func'], 'globals': []} + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec, patch.object( + targets, '_parse_module_by_name' + ) as mock_parse_module: + + mock_find_spec.return_value = mock_spec + mock_parse_module.return_value = mock_module_structure + + result = targets._parse_string("test_module:") + + assert result == ("test_module", mock_module_structure) + mock_find_spec.assert_called_once_with("test_module") + mock_parse_module.assert_called_once_with("test_module") + + def test_parse_string_class_method(self, monkeypatch): + """Test parsing class method syntax.""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "test_module" + + mock_module_structure = { + 'classes': {'TestClass': {'methods': ['some_method', 'target_method'], 'attributes': ['attr1']}}, + 'functions': ['test_func'], + 'globals': [], + } + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec, patch.object( + targets, '_parse_module_by_name' + ) as mock_parse_module: + + mock_find_spec.return_value = mock_spec + mock_parse_module.return_value = mock_module_structure + + result = targets._parse_string("test_module:TestClass.target_method()") + + expected_details = { + 'classes': {'TestClass': {'methods': ['target_method'], 'attributes': [], 'track_all': False}}, + 'functions': [], + 'globals': [], + } + + assert result == ("test_module", expected_details) + + def test_parse_string_class_attribute(self, monkeypatch): + """Test parsing class attribute syntax.""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "test_module" + + mock_module_structure = { + 'classes': {'TestClass': {'methods': ['some_method'], 'attributes': ['some_attr', 'target_attr']}}, + 'functions': ['test_func'], + 'globals': [], + } + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec, patch.object( + targets, '_parse_module_by_name' + ) as mock_parse_module: + + mock_find_spec.return_value = mock_spec + mock_parse_module.return_value = mock_module_structure + + result = targets._parse_string("test_module:TestClass.target_attr") + + expected_details = { + 'classes': {'TestClass': {'methods': [], 'attributes': ['target_attr'], 'track_all': False}}, + 'functions': [], + 'globals': [], + } + + assert result == ("test_module", expected_details) + + def test_parse_string_class_not_found_with_member(self, monkeypatch): + """Test class member syntax where class doesn't exist in module.""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "test_module" + + mock_module_structure = { + 'classes': {'OtherClass': {'methods': ['some_method'], 'attributes': ['some_attr']}}, + 'functions': ['test_func'], + 'globals': [], + } + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec, patch.object( + targets, '_parse_module_by_name' + ) as mock_parse_module: + + mock_find_spec.return_value = mock_spec + mock_parse_module.return_value = mock_module_structure + + result = targets._parse_string("test_module:NonExistentClass.some_member") + + expected_details = {'classes': {}, 'functions': [], 'globals': []} + + assert result == ("test_module", expected_details) + + def test_parse_string_function(self, monkeypatch): + """Test parsing function syntax.""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "test_module" + + mock_module_structure = {'classes': {}, 'functions': ['other_func', 'target_func'], 'globals': []} + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec, patch.object( + targets, '_parse_module_by_name' + ) as mock_parse_module: + + mock_find_spec.return_value = mock_spec + mock_parse_module.return_value = mock_module_structure + + result = targets._parse_string("test_module:target_func()") + + expected_details = {'classes': {}, 'functions': ['target_func'], 'globals': []} + + assert result == ("test_module", expected_details) + + def test_parse_string_function_not_found(self, monkeypatch): + """Test function syntax where function doesn't exist in module.""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "test_module" + + mock_module_structure = {'classes': {}, 'functions': ['other_func'], 'globals': []} + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec, patch.object( + targets, '_parse_module_by_name' + ) as mock_parse_module: + + mock_find_spec.return_value = mock_spec + mock_parse_module.return_value = mock_module_structure + + result = targets._parse_string("test_module:non_existent_func()") + + expected_details = {'classes': {}, 'functions': [], 'globals': []} + + assert result == ("test_module", expected_details) + + def test_parse_string_entire_class(self, monkeypatch): + """Test parsing entire class syntax.""" + targets = Targets([]) + + mock_spec = MagicMock() + mock_spec.name = "test_module" + + mock_module_structure = { + 'classes': {'TargetClass': {'methods': ['method1', 'method2'], 'attributes': ['attr1', 'attr2']}}, + 'functions': [], + 'globals': [], + } + + with patch('objwatch.targets.importlib.util.find_spec') as mock_find_spec, patch.object( + targets, '_parse_module_by_name' + ) as mock_parse_module: + + mock_find_spec.return_value = mock_spec + mock_parse_module.return_value = mock_module_structure + + result = targets._parse_string("test_module:TargetClass") + + expected_details = { + 'classes': {'TargetClass': {'methods': [], 'attributes': [], 'track_all': True}}, + 'functions': [], + 'globals': [], + } + + assert result == ("test_module", expected_details) diff --git a/tests/test_coverup_14.py b/tests/test_coverup_14.py new file mode 100644 index 0000000..8f20738 --- /dev/null +++ b/tests/test_coverup_14.py @@ -0,0 +1,125 @@ +# file: objwatch/config.py:12-51 +# asked: {"lines": [12, 13, 14, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 47, 48, 50, 51], "branches": [[47, 48], [47, 50], [50, 0], [50, 51]]} +# gained: {"lines": [12, 13, 14, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 47, 48, 50, 51], "branches": [[47, 48], [47, 50], [50, 0], [50, 51]]} + +import pytest +import logging +import dataclasses +from types import ModuleType +from typing import List, Union +from objwatch.config import ObjWatchConfig +from objwatch.wrappers import ABCWrapper + + +class MockWrapper(ABCWrapper): + """Mock wrapper for testing""" + + def wrap_call(self, func_name: str, frame): + return f"call:{func_name}" + + def wrap_return(self, func_name: str, result): + return f"return:{func_name}" + + def wrap_upd(self, old_value, current_value): + return f"old:{old_value}", f"new:{current_value}" + + +class TestObjWatchConfig: + """Test cases for ObjWatchConfig class""" + + def test_init_with_valid_targets(self): + """Test initialization with valid targets""" + config = ObjWatchConfig(targets=["test_module"]) + assert config.targets == ["test_module"] + assert config.exclude_targets is None + assert config.framework is None + assert config.indexes is None + assert config.output is None + assert config.output_xml is None + assert config.level == logging.DEBUG + assert config.simple is False + assert config.wrapper is None + assert config.with_locals is False + assert config.with_globals is False + + def test_init_with_module_target(self): + """Test initialization with module target""" + test_module = ModuleType("test_module") + config = ObjWatchConfig(targets=[test_module]) + assert config.targets == [test_module] + + def test_init_with_mixed_targets(self): + """Test initialization with mixed string and module targets""" + test_module = ModuleType("test_module") + config = ObjWatchConfig(targets=["string_target", test_module]) + assert len(config.targets) == 2 + assert "string_target" in config.targets + assert test_module in config.targets + + def test_init_with_all_parameters(self): + """Test initialization with all parameters specified""" + test_module = ModuleType("test_module") + wrapper = MockWrapper() + + config = ObjWatchConfig( + targets=["main_module", test_module], + exclude_targets=["excluded_module"], + framework="torch", + indexes=[0, 1], + output="log.txt", + output_xml="log.xml", + level=logging.INFO, + simple=True, + wrapper=wrapper, + with_locals=True, + with_globals=True, + ) + + assert len(config.targets) == 2 + assert config.exclude_targets == ["excluded_module"] + assert config.framework == "torch" + assert config.indexes == [0, 1] + assert config.output == "log.txt" + assert config.output_xml == "log.xml" + assert config.level == logging.INFO + assert config.simple is True + assert config.wrapper == wrapper + assert config.with_locals is True + assert config.with_globals is True + + def test_post_init_empty_targets_raises_value_error(self): + """Test that empty targets list raises ValueError""" + with pytest.raises(ValueError, match="At least one monitoring target must be specified"): + ObjWatchConfig(targets=[]) + + def test_post_init_none_targets_raises_value_error(self): + """Test that None targets raises ValueError""" + with pytest.raises(ValueError, match="At least one monitoring target must be specified"): + ObjWatchConfig(targets=None) + + def test_post_init_level_force_with_output_raises_value_error(self): + """Test that level='force' with output specified raises ValueError""" + with pytest.raises(ValueError, match="output cannot be specified when level is 'force'"): + ObjWatchConfig(targets=["test_module"], level="force", output="log.txt") + + def test_post_init_level_force_without_output_succeeds(self): + """Test that level='force' without output succeeds""" + config = ObjWatchConfig(targets=["test_module"], level="force") + assert config.level == "force" + assert config.output is None + + def test_post_init_level_not_force_with_output_succeeds(self): + """Test that level not 'force' with output succeeds""" + config = ObjWatchConfig(targets=["test_module"], level=logging.INFO, output="log.txt") + assert config.level == logging.INFO + assert config.output == "log.txt" + + def test_frozen_dataclass_prevents_modification(self): + """Test that frozen dataclass prevents attribute modification""" + config = ObjWatchConfig(targets=["test_module"]) + + with pytest.raises(dataclasses.FrozenInstanceError): + config.targets = ["new_target"] + + with pytest.raises(dataclasses.FrozenInstanceError): + config.level = logging.INFO diff --git a/tests/test_coverup_15.py b/tests/test_coverup_15.py new file mode 100644 index 0000000..d5d3503 --- /dev/null +++ b/tests/test_coverup_15.py @@ -0,0 +1,89 @@ +# file: objwatch/tracer.py:238-254 +# asked: {"lines": [238, 248, 249, 250, 251, 252, 253, 254], "branches": [[248, 249], [248, 254], [249, 250], [249, 252]]} +# gained: {"lines": [238, 248, 249, 250, 251, 252, 253, 254], "branches": [[248, 249], [248, 254], [249, 250], [249, 252]]} + +import pytest +from unittest.mock import Mock, patch +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig +from objwatch.wrappers import ABCWrapper +from objwatch.utils.logger import log_warn, log_error + + +class TestWrapper(ABCWrapper): + """Test wrapper implementation for testing.""" + + def wrap_call(self, func_name, frame): + return f"call:{func_name}" + + def wrap_return(self, func_name, result): + return f"return:{func_name}:{result}" + + def wrap_upd(self, old_value, current_value): + return f"old:{old_value}", f"new:{current_value}" + + +class InvalidWrapper: + """Invalid wrapper that is not a subclass of ABCWrapper.""" + + pass + + +class TestTracerLoadWrapper: + """Test cases for Tracer.load_wrapper method.""" + + def test_load_wrapper_with_valid_subclass(self, monkeypatch): + """Test loading a valid wrapper subclass of ABCWrapper.""" + config = ObjWatchConfig(targets=["test_module"]) + tracer = Tracer(config) + + # Mock the log_warn function to verify it's called + mock_log_warn = Mock() + monkeypatch.setattr('objwatch.tracer.log_warn', mock_log_warn) + + # Load the valid wrapper + result = tracer.load_wrapper(TestWrapper) + + # Verify the wrapper was initialized and returned + assert isinstance(result, TestWrapper) + + # Verify log_warn was called with the correct message + mock_log_warn.assert_called_once_with(f"wrapper 'TestWrapper' loaded") + + def test_load_wrapper_with_invalid_wrapper(self, monkeypatch): + """Test loading an invalid wrapper that is not a subclass of ABCWrapper.""" + config = ObjWatchConfig(targets=["test_module"]) + tracer = Tracer(config) + + # Mock the log_error function to verify it's called + mock_log_error = Mock() + monkeypatch.setattr('objwatch.tracer.log_error', mock_log_error) + + # Attempt to load invalid wrapper and expect ValueError + with pytest.raises(ValueError, match="wrapper 'InvalidWrapper' is not a subclass of ABCWrapper"): + tracer.load_wrapper(InvalidWrapper) + + # Verify log_error was called with the correct message + mock_log_error.assert_called_once_with(f"wrapper 'InvalidWrapper' is not a subclass of ABCWrapper") + + def test_load_wrapper_with_none(self): + """Test loading None as wrapper (should return None).""" + config = ObjWatchConfig(targets=["test_module"]) + tracer = Tracer(config) + + # Load None wrapper + result = tracer.load_wrapper(None) + + # Verify None is returned + assert result is None + + def test_load_wrapper_with_false(self): + """Test loading False as wrapper (should return None).""" + config = ObjWatchConfig(targets=["test_module"]) + tracer = Tracer(config) + + # Load False wrapper + result = tracer.load_wrapper(False) + + # Verify None is returned + assert result is None diff --git a/tests/test_coverup_16.py b/tests/test_coverup_16.py new file mode 100644 index 0000000..30c8370 --- /dev/null +++ b/tests/test_coverup_16.py @@ -0,0 +1,64 @@ +# file: objwatch/mp_handls.py:97-109 +# asked: {"lines": [97, 102, 104, 105, 106, 107, 108, 109], "branches": [[105, 0], [105, 106]]} +# gained: {"lines": [97, 102, 104, 105, 106, 107, 108, 109], "branches": [[105, 0], [105, 106]]} + +import pytest +import multiprocessing +from unittest.mock import patch, MagicMock +from objwatch.mp_handls import MPHandls + + +def test_check_init_multiprocessing_non_main_process(): + """Test _check_init_multiprocessing when current process is not MainProcess.""" + with patch('multiprocessing.current_process') as mock_current_process: + # Mock a non-main process with identity + mock_process = MagicMock() + mock_process.name = "Process-1" + mock_process._identity = [2] # Process identity (starts from 1) + mock_current_process.return_value = mock_process + + # Create MPHandls instance and call the method + handler = MPHandls() + handler._check_init_multiprocessing() + + # Verify the state was set correctly + assert handler.initialized is True + assert handler.index == 1 # identity[0] - 1 = 2 - 1 = 1 + assert handler.sync_fn is None + + +def test_check_init_multiprocessing_main_process(): + """Test _check_init_multiprocessing when current process is MainProcess.""" + with patch('multiprocessing.current_process') as mock_current_process: + # Mock the main process + mock_process = MagicMock() + mock_process.name = "MainProcess" + mock_current_process.return_value = mock_process + + # Create MPHandls instance and call the method + handler = MPHandls() + handler._check_init_multiprocessing() + + # Verify the state was not changed (should remain default) + assert handler.initialized is False + assert handler.index is None + assert handler.sync_fn is None + + +def test_check_init_multiprocessing_with_logging(): + """Test _check_init_multiprocessing verifies logging is called.""" + with patch('multiprocessing.current_process') as mock_current_process, patch( + 'objwatch.mp_handls.log_info' + ) as mock_log_info: + # Mock a non-main process with identity + mock_process = MagicMock() + mock_process.name = "Process-1" + mock_process._identity = [3] # Process identity (starts from 1) + mock_current_process.return_value = mock_process + + # Create MPHandls instance and call the method + handler = MPHandls() + handler._check_init_multiprocessing() + + # Verify logging was called with correct message + mock_log_info.assert_called_once_with("multiprocessing initialized. index: 2") diff --git a/tests/test_coverup_17.py b/tests/test_coverup_17.py new file mode 100644 index 0000000..dce505b --- /dev/null +++ b/tests/test_coverup_17.py @@ -0,0 +1,141 @@ +# file: objwatch/event_handls.py:196-235 +# asked: {"lines": [196, 198, 199, 200, 201, 202, 203, 204, 205, 206, 220, 221, 222, 223, 225, 226, 227, 228, 229, 230, 231, 232, 235], "branches": [[225, 0], [225, 226]]} +# gained: {"lines": [196, 198, 199, 200, 201, 202, 203, 204, 205, 206, 220, 221, 222, 223, 225, 226, 227, 228, 229, 230, 231, 232, 235], "branches": [[225, 0], [225, 226]]} + +import pytest +import xml.etree.ElementTree as ET +from unittest.mock import patch, MagicMock +from objwatch.event_handls import EventHandls +from objwatch.events import EventType + + +class TestEventHandlsHandlePop: + """Test cases for EventHandls.handle_pop method.""" + + def test_handle_pop_without_xml_output(self, monkeypatch): + """Test handle_pop when output_xml is None (lines 220-223).""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Execute + event_handls.handle_pop( + lineno=42, + class_name="TestClass", + key="test_list", + value_type=list, + old_value_len=5, + current_value_len=4, + call_depth=2, + index_info="[0]", + ) + + # Verify + expected_msg = "[0] 42 | | pop TestClass.test_list (list)(len)5 -> 4" + mock_log_debug.assert_called_once_with(expected_msg) + + def test_handle_pop_with_xml_output(self, monkeypatch): + """Test handle_pop when output_xml is set (lines 220-235).""" + # Setup + event_handls = EventHandls(output_xml="test.xml") + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Create a mock current_node with at least one element + mock_element = ET.Element('root') + event_handls.current_node = [mock_element] + + # Execute + event_handls.handle_pop( + lineno=42, + class_name="TestClass", + key="test_list", + value_type=list, + old_value_len=5, + current_value_len=4, + call_depth=2, + index_info="[0]", + ) + + # Verify debug logging + expected_msg = "[0] 42 | | pop TestClass.test_list (list)(len)5 -> 4" + mock_log_debug.assert_called_once_with(expected_msg) + + # Verify XML element creation + assert len(mock_element) == 1 + pop_element = mock_element[0] + assert pop_element.tag == "pop" + assert pop_element.attrib['name'] == "TestClass.test_list" + assert pop_element.attrib['line'] == "42" + assert pop_element.attrib['old'] == "(list)(len)5" + assert pop_element.attrib['new'] == "(list)(len)4" + + def test_handle_pop_with_none_lengths(self, monkeypatch): + """Test handle_pop with None values for old_value_len and current_value_len.""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Execute + event_handls.handle_pop( + lineno=42, + class_name="TestClass", + key="test_list", + value_type=list, + old_value_len=None, + current_value_len=None, + call_depth=1, + index_info="", + ) + + # Verify + expected_msg = " 42 | pop TestClass.test_list (list)(len)None -> None" + mock_log_debug.assert_called_once_with(expected_msg) + + def test_handle_pop_with_zero_call_depth(self, monkeypatch): + """Test handle_pop with call_depth=0.""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Execute + event_handls.handle_pop( + lineno=42, + class_name="TestClass", + key="test_list", + value_type=list, + old_value_len=3, + current_value_len=2, + call_depth=0, + index_info="[1]", + ) + + # Verify + expected_msg = "[1] 42 pop TestClass.test_list (list)(len)3 -> 2" + mock_log_debug.assert_called_once_with(expected_msg) + + def test_handle_pop_with_different_value_types(self, monkeypatch): + """Test handle_pop with different value types.""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Test with dict type + event_handls.handle_pop( + lineno=42, + class_name="TestClass", + key="test_dict", + value_type=dict, + old_value_len=10, + current_value_len=9, + call_depth=1, + index_info="", + ) + + # Verify + expected_msg = " 42 | pop TestClass.test_dict (dict)(len)10 -> 9" + mock_log_debug.assert_called_once_with(expected_msg) diff --git a/tests/test_coverup_18.py b/tests/test_coverup_18.py new file mode 100644 index 0000000..fc1de28 --- /dev/null +++ b/tests/test_coverup_18.py @@ -0,0 +1,66 @@ +# file: objwatch/events.py:7-29 +# asked: {"lines": [7, 8, 13, 16, 19, 22, 25, 27, 28, 29], "branches": []} +# gained: {"lines": [7, 8, 13, 16, 19, 22, 25, 27, 28, 29], "branches": []} + +import pytest + + +def test_event_type_enum_members(): + """Test that all EventType enum members are properly defined and have correct labels.""" + from objwatch.events import EventType + + # Test RUN member + assert EventType.RUN.value == 1 + assert EventType.RUN.label == 'run' + + # Test END member + assert EventType.END.value == 2 + assert EventType.END.label == 'end' + + # Test UPD member + assert EventType.UPD.value == 3 + assert EventType.UPD.label == 'upd' + + # Test APD member + assert EventType.APD.value == 4 + assert EventType.APD.label == 'apd' + + # Test POP member + assert EventType.POP.value == 5 + assert EventType.POP.label == 'pop' + + +def test_event_type_enum_iteration(): + """Test that EventType enum can be iterated and all members are accessible.""" + from objwatch.events import EventType + + expected_members = [(1, 'run'), (2, 'end'), (3, 'upd'), (4, 'apd'), (5, 'pop')] + + for event_type, (expected_value, expected_label) in zip(EventType, expected_members): + assert event_type.value == expected_value + assert event_type.label == expected_label + + +def test_event_type_enum_lookup(): + """Test that EventType enum members can be looked up by value.""" + from objwatch.events import EventType + + assert EventType(1) is EventType.RUN + assert EventType(2) is EventType.END + assert EventType(3) is EventType.UPD + assert EventType(4) is EventType.APD + assert EventType(5) is EventType.POP + + +def test_event_type_enum_comparison(): + """Test that EventType enum members can be compared.""" + from objwatch.events import EventType + + assert EventType.RUN == EventType.RUN + assert EventType.END == EventType.END + assert EventType.UPD == EventType.UPD + assert EventType.APD == EventType.APD + assert EventType.POP == EventType.POP + + assert EventType.RUN != EventType.END + assert EventType.END != EventType.UPD diff --git a/tests/test_coverup_19.py b/tests/test_coverup_19.py new file mode 100644 index 0000000..80733b6 --- /dev/null +++ b/tests/test_coverup_19.py @@ -0,0 +1,94 @@ +# file: objwatch/targets.py:417-437 +# asked: {"lines": [417, 428, 429, 431, 432, 433, 434, 435, 436, 437], "branches": [[428, 429], [428, 431], [431, 0], [431, 432], [432, 433], [432, 434], [434, 431], [434, 435], [435, 431], [435, 436], [436, 435], [436, 437]]} +# gained: {"lines": [417, 428, 429, 431, 432, 433, 434, 435, 436, 437], "branches": [[428, 429], [428, 431], [431, 0], [431, 432], [432, 433], [432, 434], [434, 435], [435, 431], [435, 436], [436, 435], [436, 437]]} + +import ast +import pytest +from objwatch.targets import Targets + + +class TestTargetsProcessAssignment: + def test_process_assignment_simple_assignment(self): + """Test simple assignment: var = value""" + targets = Targets([]) + result = {'globals': []} + node = ast.parse("x = 42").body[0] + targets._process_assignment(node, result) + assert result['globals'] == ['x'] + + def test_process_assignment_tuple_unpacking(self): + """Test tuple unpacking: a, b = (1, 2)""" + targets = Targets([]) + result = {'globals': []} + node = ast.parse("a, b = (1, 2)").body[0] + targets._process_assignment(node, result) + assert sorted(result['globals']) == ['a', 'b'] + + def test_process_assignment_inside_function(self, monkeypatch): + """Test assignment inside function definition (should be skipped)""" + targets = Targets([]) + result = {'globals': []} + + # Create a function with an assignment inside + func_code = """ +def test_func(): + x = 42 +""" + func_node = ast.parse(func_code).body[0] + assignment_node = func_node.body[0] + + # Mock iter_parents to return function as parent + def mock_iter_parents(node): + return [func_node] + + monkeypatch.setattr('objwatch.targets.iter_parents', mock_iter_parents) + targets._process_assignment(assignment_node, result) + assert result['globals'] == [] + + def test_process_assignment_inside_class(self, monkeypatch): + """Test assignment inside class definition (should be skipped)""" + targets = Targets([]) + result = {'globals': []} + + # Create a class with an assignment inside + class_code = """ +class TestClass: + x = 42 +""" + class_node = ast.parse(class_code).body[0] + assignment_node = class_node.body[0] + + # Mock iter_parents to return class as parent + def mock_iter_parents(node): + return [class_node] + + monkeypatch.setattr('objwatch.targets.iter_parents', mock_iter_parents) + targets._process_assignment(assignment_node, result) + assert result['globals'] == [] + + def test_process_assignment_complex_tuple(self): + """Test complex tuple unpacking with nested structure - only top-level names are captured""" + targets = Targets([]) + result = {'globals': []} + node = ast.parse("a, (b, c) = (1, (2, 3))").body[0] + targets._process_assignment(node, result) + # The current implementation only captures top-level names in tuples + # Nested tuples like (b, c) are not processed recursively + assert result['globals'] == ['a'] + + def test_process_assignment_multiple_targets(self): + """Test assignment with multiple targets: x = y = 42""" + targets = Targets([]) + result = {'globals': []} + node = ast.parse("x = y = 42").body[0] + targets._process_assignment(node, result) + assert sorted(result['globals']) == ['x', 'y'] + + def test_process_assignment_tuple_with_non_name_elements(self): + """Test tuple unpacking with non-name elements (should be ignored)""" + targets = Targets([]) + result = {'globals': []} + node = ast.parse("a, b[0] = (1, 2)").body[0] + targets._process_assignment(node, result) + # Only 'a' should be captured, 'b[0]' is not a Name node + assert result['globals'] == ['a'] diff --git a/tests/test_coverup_2.py b/tests/test_coverup_2.py new file mode 100644 index 0000000..ee84509 --- /dev/null +++ b/tests/test_coverup_2.py @@ -0,0 +1,70 @@ +# file: objwatch/targets.py:94-114 +# asked: {"lines": [94, 95, 96, 107, 108, 109, 110, 111, 112, 113, 114], "branches": [[107, 108], [107, 109], [109, 110], [109, 111], [111, 112], [111, 114], [112, 111], [112, 113]]} +# gained: {"lines": [94, 95, 96, 107, 108, 109, 110, 111, 112, 113, 114], "branches": [[107, 108], [107, 109], [109, 110], [109, 111], [111, 112], [111, 114], [112, 111], [112, 113]]} + +import pytest +from unittest.mock import patch, MagicMock +from objwatch.targets import Targets +from objwatch.utils.logger import log_error +from typing import Optional, List, Union + +TargetsType = Union[str, List[str], None] + + +class TestTargetsCheckTargets: + """Test cases for Targets._check_targets method.""" + + def test_check_targets_with_string_targets(self): + """Test _check_targets when targets is a string.""" + targets = Targets([], []) + result_targets, result_exclude = targets._check_targets("module_name", None) + assert result_targets == ["module_name"] + assert result_exclude is None + + def test_check_targets_with_string_exclude_targets(self): + """Test _check_targets when exclude_targets is a string.""" + targets = Targets([], []) + result_targets, result_exclude = targets._check_targets(["module1"], "module2") + assert result_targets == ["module1"] + assert result_exclude == ["module2"] + + def test_check_targets_with_py_file_in_exclude_targets(self): + """Test _check_targets when exclude_targets contains .py files.""" + targets = Targets([], []) + with patch('objwatch.targets.log_error') as mock_log_error: + result_targets, result_exclude = targets._check_targets(["module1"], ["file.py", "module2"]) + assert result_targets == ["module1"] + assert result_exclude == ["file.py", "module2"] + mock_log_error.assert_called_once_with("Unsupported .py files in exclude_target") + + def test_check_targets_with_multiple_py_files_in_exclude_targets(self): + """Test _check_targets when exclude_targets contains multiple .py files.""" + targets = Targets([], []) + with patch('objwatch.targets.log_error') as mock_log_error: + result_targets, result_exclude = targets._check_targets(["module1"], ["file1.py", "file2.py", "module2"]) + assert result_targets == ["module1"] + assert result_exclude == ["file1.py", "file2.py", "module2"] + assert mock_log_error.call_count == 2 + + def test_check_targets_with_none_exclude_targets(self): + """Test _check_targets when exclude_targets is None.""" + targets = Targets([], []) + result_targets, result_exclude = targets._check_targets(["module1"], None) + assert result_targets == ["module1"] + assert result_exclude is None + + def test_check_targets_with_empty_exclude_targets(self): + """Test _check_targets when exclude_targets is empty list.""" + targets = Targets([], []) + result_targets, result_exclude = targets._check_targets(["module1"], []) + assert result_targets == ["module1"] + assert result_exclude == [] + + def test_check_targets_with_both_strings(self): + """Test _check_targets when both targets and exclude_targets are strings.""" + targets = Targets([], []) + with patch('objwatch.targets.log_error') as mock_log_error: + result_targets, result_exclude = targets._check_targets("module1", "file.py") + assert result_targets == ["module1"] + assert result_exclude == ["file.py"] + mock_log_error.assert_called_once_with("Unsupported .py files in exclude_target") diff --git a/tests/test_coverup_20.py b/tests/test_coverup_20.py new file mode 100644 index 0000000..51397d2 --- /dev/null +++ b/tests/test_coverup_20.py @@ -0,0 +1,100 @@ +# file: objwatch/targets.py:138-154 +# asked: {"lines": [138, 148, 149, 150, 151, 152, 153, 154], "branches": [[148, 149], [148, 150], [150, 151], [150, 152], [152, 153], [152, 154]]} +# gained: {"lines": [138, 148, 149, 150, 151, 152, 153, 154], "branches": [[148, 149], [148, 150], [150, 151], [150, 152], [152, 153], [152, 154]]} + +import pytest +from types import ModuleType, FunctionType, MethodType +import sys +import types + + +class TestTargetsParseTarget: + + def test_parse_target_module_type(self, monkeypatch): + """Test _parse_target with ModuleType target""" + from objwatch.targets import Targets + + # Create a mock module + mock_module = ModuleType('test_module') + mock_module.__name__ = 'test_module' + + # Mock the _parse_module method + targets = Targets([]) + expected_result = ('test_module', {'parsed': 'structure'}) + monkeypatch.setattr(targets, '_parse_module', lambda x: expected_result) + + result = targets._parse_target(mock_module) + assert result == expected_result + + def test_parse_target_class_type(self, monkeypatch): + """Test _parse_target with ClassType target""" + from objwatch.targets import Targets + + # Create a mock class + class MockClass: + pass + + # Mock the _parse_class method + targets = Targets([]) + expected_result = ( + 'test_module', + {'classes': {'MockClass': {'methods': [], 'attributes': [], 'track_all': True}}}, + ) + monkeypatch.setattr(targets, '_parse_class', lambda x: expected_result) + + result = targets._parse_target(MockClass) + assert result == expected_result + + def test_parse_target_function_type(self, monkeypatch): + """Test _parse_target with FunctionType target""" + from objwatch.targets import Targets + + # Create a mock function + def mock_function(): + pass + + # Mock the _parse_function method + targets = Targets([]) + expected_result = ('test_module', {'classes': {}, 'functions': ['mock_function'], 'globals': []}) + monkeypatch.setattr(targets, '_parse_function', lambda x: expected_result) + + result = targets._parse_target(mock_function) + assert result == expected_result + + def test_parse_target_method_type(self, monkeypatch): + """Test _parse_target with MethodType target""" + from objwatch.targets import Targets + + # Create a mock class with method + class MockClass: + def mock_method(self): + pass + + mock_method = MockClass().mock_method + + # Mock the _parse_function method + targets = Targets([]) + expected_result = ( + 'test_module', + { + 'classes': {'MockClass': {'methods': ['mock_method'], 'attributes': [], 'track_all': False}}, + 'functions': [], + 'globals': [], + }, + ) + monkeypatch.setattr(targets, '_parse_function', lambda x: expected_result) + + result = targets._parse_target(mock_method) + assert result == expected_result + + def test_parse_target_string(self, monkeypatch): + """Test _parse_target with string target""" + from objwatch.targets import Targets + + # Mock the _parse_string method + targets = Targets([]) + expected_result = ('test_module', {'classes': {}, 'functions': [], 'globals': []}) + monkeypatch.setattr(targets, '_parse_string', lambda x: expected_result) + + result = targets._parse_target('test_module:SomeClass') + assert result == expected_result diff --git a/tests/test_coverup_21.py b/tests/test_coverup_21.py new file mode 100644 index 0000000..a3a2ac9 --- /dev/null +++ b/tests/test_coverup_21.py @@ -0,0 +1,207 @@ +# file: objwatch/event_handls.py:155-194 +# asked: {"lines": [155, 157, 158, 159, 160, 161, 162, 163, 164, 165, 179, 180, 181, 182, 184, 185, 186, 187, 188, 189, 190, 191, 194], "branches": [[184, 0], [184, 185]]} +# gained: {"lines": [155, 157, 158, 159, 160, 161, 162, 163, 164, 165, 179, 180, 181, 182, 184, 185, 186, 187, 188, 189, 190, 191, 194], "branches": [[184, 0], [184, 185]]} + +import pytest +import xml.etree.ElementTree as ET +from unittest.mock import patch, MagicMock +from objwatch.event_handls import EventHandls +from objwatch.events import EventType +from objwatch.utils.logger import log_debug + + +class TestEventHandlsHandleApd: + """Test cases for EventHandls.handle_apd method to achieve full coverage.""" + + def test_handle_apd_without_xml_output(self, monkeypatch): + """Test handle_apd when output_xml is None (lines 179-182).""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Test parameters + lineno = 42 + class_name = "TestClass" + key = "test_list" + value_type = list + old_value_len = 3 + current_value_len = 5 + call_depth = 2 + index_info = "[0]" + + # Execute + event_handls.handle_apd( + lineno=lineno, + class_name=class_name, + key=key, + value_type=value_type, + old_value_len=old_value_len, + current_value_len=current_value_len, + call_depth=call_depth, + index_info=index_info, + ) + + # Verify + expected_msg = f"{class_name}.{key} ({value_type.__name__})(len){old_value_len} -> {current_value_len}" + expected_prefix = f"{lineno:>5} " + "| " * call_depth + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert index_info in call_args + assert expected_prefix in call_args + assert EventType.APD.label in call_args + assert expected_msg in call_args + + def test_handle_apd_with_xml_output(self, monkeypatch): + """Test handle_apd when output_xml is set (lines 179-194).""" + # Setup + event_handls = EventHandls(output_xml="test.xml") + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Create a mock current_node with at least one element + mock_parent = ET.Element('parent') + event_handls.current_node = [mock_parent] + + # Test parameters + lineno = 42 + class_name = "TestClass" + key = "test_list" + value_type = list + old_value_len = 3 + current_value_len = 5 + call_depth = 2 + index_info = "[0]" + + # Execute + event_handls.handle_apd( + lineno=lineno, + class_name=class_name, + key=key, + value_type=value_type, + old_value_len=old_value_len, + current_value_len=current_value_len, + call_depth=call_depth, + index_info=index_info, + ) + + # Verify debug logging + mock_log_debug.assert_called_once() + + # Verify XML structure + assert len(mock_parent) == 1 + apd_element = mock_parent[0] + assert apd_element.tag == EventType.APD.label + assert apd_element.attrib['name'] == f"{class_name}.{key}" + assert apd_element.attrib['line'] == str(lineno) + assert apd_element.attrib['old'] == f"({value_type.__name__})(len){old_value_len}" + assert apd_element.attrib['new'] == f"({value_type.__name__})(len){current_value_len}" + + def test_handle_apd_with_none_lengths(self, monkeypatch): + """Test handle_apd with None values for old_value_len and current_value_len.""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Test parameters + lineno = 42 + class_name = "TestClass" + key = "test_list" + value_type = list + old_value_len = None + current_value_len = None + call_depth = 1 + index_info = "" + + # Execute + event_handls.handle_apd( + lineno=lineno, + class_name=class_name, + key=key, + value_type=value_type, + old_value_len=old_value_len, + current_value_len=current_value_len, + call_depth=call_depth, + index_info=index_info, + ) + + # Verify + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert f"({value_type.__name__})(len){old_value_len} -> {current_value_len}" in call_args + + def test_handle_apd_with_zero_call_depth(self, monkeypatch): + """Test handle_apd with call_depth=0.""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Test parameters + lineno = 42 + class_name = "TestClass" + key = "test_list" + value_type = list + old_value_len = 0 + current_value_len = 1 + call_depth = 0 + index_info = "[1]" + + # Execute + event_handls.handle_apd( + lineno=lineno, + class_name=class_name, + key=key, + value_type=value_type, + old_value_len=old_value_len, + current_value_len=current_value_len, + call_depth=call_depth, + index_info=index_info, + ) + + # Verify + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert f"{lineno:>5} " in call_args # Should not have any pipes for depth 0 + assert "| " not in call_args + assert index_info in call_args + + def test_handle_apd_with_different_types(self, monkeypatch): + """Test handle_apd with different value types.""" + # Setup + event_handls = EventHandls(output_xml=None) + mock_log_debug = MagicMock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Test with different types + test_cases = [(dict, "dict"), (set, "set"), (tuple, "tuple"), (str, "str")] + + for value_type, type_name in test_cases: + mock_log_debug.reset_mock() + + # Test parameters + lineno = 10 + class_name = "Container" + key = "data" + old_value_len = 1 + current_value_len = 2 + call_depth = 1 + index_info = "" + + # Execute + event_handls.handle_apd( + lineno=lineno, + class_name=class_name, + key=key, + value_type=value_type, + old_value_len=old_value_len, + current_value_len=current_value_len, + call_depth=call_depth, + index_info=index_info, + ) + + # Verify + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert f"({type_name})(len){old_value_len} -> {current_value_len}" in call_args diff --git a/tests/test_coverup_22.py b/tests/test_coverup_22.py new file mode 100644 index 0000000..e1dbf64 --- /dev/null +++ b/tests/test_coverup_22.py @@ -0,0 +1,89 @@ +# file: objwatch/tracer.py:423-440 +# asked: {"lines": [423, 432, 434, 435, 437, 438, 440], "branches": [[434, 435], [434, 437], [437, 438], [437, 440]]} +# gained: {"lines": [423, 432, 434, 435, 437, 438, 440], "branches": [[434, 435], [434, 437], [437, 438], [437, 440]]} + +import pytest +from types import FrameType +from unittest.mock import Mock, MagicMock +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerGlobalChanges: + """Test cases for Tracer._check_global_changes method""" + + def test_check_global_changes_no_global_index_with_globals_true(self, monkeypatch): + """Test when global_index is empty and with_globals is True""" + config = ObjWatchConfig(targets=['test_module'], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {} + tracer.builtin_fields = {'__builtins__', '__name__', 'self'} + + # Create a mock frame with globals containing non-builtin variables + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module', 'custom_var': 'value', '__builtins__': {}} + + result = tracer._check_global_changes(mock_frame) + assert result is True + + def test_check_global_changes_no_global_index_with_globals_true_only_builtins(self, monkeypatch): + """Test when global_index is empty and with_globals is True but only builtin variables exist""" + config = ObjWatchConfig(targets=['test_module'], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {} + tracer.builtin_fields = {'__builtins__', '__name__', 'self'} + + # Create a mock frame with only builtin globals + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module', '__builtins__': {}, 'self': None} + + result = tracer._check_global_changes(mock_frame) + assert result is False + + def test_check_global_changes_with_globals_false(self, monkeypatch): + """Test when with_globals is False""" + config = ObjWatchConfig(targets=['test_module'], with_globals=False) + tracer = Tracer(config) + tracer.global_index = {'test_module': True} + + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module'} + + result = tracer._check_global_changes(mock_frame) + assert result is False + + def test_check_global_changes_with_globals_true_module_in_global_index(self, monkeypatch): + """Test when with_globals is True and module exists in global_index""" + config = ObjWatchConfig(targets=['test_module'], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {'test_module': True} + + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module'} + + result = tracer._check_global_changes(mock_frame) + assert result is True + + def test_check_global_changes_with_globals_true_module_not_in_global_index(self, monkeypatch): + """Test when with_globals is True but module not in global_index""" + config = ObjWatchConfig(targets=['test_module'], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {'other_module': True} + + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module'} + + result = tracer._check_global_changes(mock_frame) + assert result is False + + def test_check_global_changes_empty_module_name(self, monkeypatch): + """Test when module name is empty""" + config = ObjWatchConfig(targets=['test_module'], with_globals=True) + tracer = Tracer(config) + tracer.global_index = {'test_module': True} + + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': ''} + + result = tracer._check_global_changes(mock_frame) + assert result is False diff --git a/tests/test_coverup_23.py b/tests/test_coverup_23.py new file mode 100644 index 0000000..1f6b8ad --- /dev/null +++ b/tests/test_coverup_23.py @@ -0,0 +1,117 @@ +# file: objwatch/utils/weak.py:57-79 +# asked: {"lines": [57, 58, 60, 62, 63, 64, 65, 66, 68, 69, 70, 71, 73, 75, 76, 77, 78, 79], "branches": [[64, 0], [64, 65], [65, 66], [65, 68], [78, 0], [78, 79]]} +# gained: {"lines": [57, 58, 60, 62, 63, 64, 65, 66, 68, 69, 70, 71, 73, 75, 76, 77, 78, 79], "branches": [[64, 0], [64, 65], [65, 66], [65, 68], [78, 0], [78, 79]]} + +import pytest +import gc +from weakref import ref +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryInit: + + def test_init_with_dict_parameter(self): + """Test that __init__ processes dict parameter correctly (lines 78-79)""" + + # Create a custom class that can be weakly referenced + class TestKey: + def __init__(self, name): + self.name = name + + key1 = TestKey("key1") + key2 = TestKey("key2") + test_dict = {key1: "value1", key2: "value2"} + weak_dict = WeakIdKeyDictionary(dict=test_dict) + + # Verify the dictionary was updated with the provided dict + assert len(weak_dict) == 2 + assert weak_dict[key1] == "value1" + assert weak_dict[key2] == "value2" + + def test_remove_callback_during_iteration(self, monkeypatch): + """Test remove callback when _iterating is True (lines 65-66)""" + weak_dict = WeakIdKeyDictionary() + + # Mock _iterating to be True to trigger the pending removals path + weak_dict._iterating = True + weak_dict._pending_removals = [] + + # Create a test key and add it to data + test_key = object() + weak_dict.data[test_key] = "test_value" + + # Call the remove callback directly + weak_dict._remove(test_key) + + # Verify the key was added to pending removals instead of being deleted + assert test_key in weak_dict._pending_removals + assert test_key in weak_dict.data # Should still be in data + + def test_remove_callback_key_not_found(self, monkeypatch): + """Test remove callback when key doesn't exist in data (lines 68-71)""" + weak_dict = WeakIdKeyDictionary() + + # Ensure _iterating is False to trigger the deletion path + weak_dict._iterating = False + + # Create a test key that is NOT in data + test_key = object() + + # This should not raise an exception due to the KeyError catch + weak_dict._remove(test_key) + + # Verify no side effects + assert len(weak_dict.data) == 0 + + def test_remove_callback_successful_deletion(self): + """Test remove callback successfully deletes existing key (lines 68-69)""" + weak_dict = WeakIdKeyDictionary() + + # Ensure _iterating is False to trigger the deletion path + weak_dict._iterating = False + + # Create a test key and add it to data + test_key = object() + weak_dict.data[test_key] = "test_value" + + # Call the remove callback + weak_dict._remove(test_key) + + # Verify the key was deleted from data + assert test_key not in weak_dict.data + assert len(weak_dict.data) == 0 + + def test_remove_callback_self_destroyed(self): + """Test remove callback when self has been garbage collected (lines 63-64)""" + # Create a weak reference to track the dictionary + weak_dict = WeakIdKeyDictionary() + weak_ref = ref(weak_dict) + + # Add some data + test_key = object() + weak_dict.data[test_key] = "test_value" + + # Get the remove callback function + remove_callback = weak_dict._remove + + # Delete the dictionary to trigger garbage collection + del weak_dict + gc.collect() + + # Verify the dictionary was destroyed + assert weak_ref() is None + + # Call the remove callback - should do nothing since self is None + remove_callback(test_key) + # No assertions needed - just verifying no exceptions are raised + + def test_custom_ref_type(self): + """Test initialization with custom ref_type parameter (line 60)""" + + class CustomRefType: + def __init__(self, key, callback=None): + self.key = key + self.callback = callback + + weak_dict = WeakIdKeyDictionary(ref_type=CustomRefType) + assert weak_dict.ref_type is CustomRefType diff --git a/tests/test_coverup_24.py b/tests/test_coverup_24.py new file mode 100644 index 0000000..52a0b7d --- /dev/null +++ b/tests/test_coverup_24.py @@ -0,0 +1,48 @@ +# file: objwatch/tracer.py:95-104 +# asked: {"lines": [95, 96, 97, 98, 99, 100, 104], "branches": [[97, 98], [97, 104]]} +# gained: {"lines": [95, 96, 97, 98, 99, 100, 104], "branches": [[97, 98], [97, 104]]} + +import pytest +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerCallDepthSetter: + """Test cases for Tracer.call_depth setter to achieve full coverage.""" + + def test_call_depth_setter_valid_positive_value(self): + """Test setting call_depth with a valid positive value.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Set a valid positive call depth + tracer.call_depth = 5 + + # Verify the value was set correctly + assert tracer._call_depth == 5 + + def test_call_depth_setter_valid_zero_value(self): + """Test setting call_depth with zero value.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Set call depth to zero + tracer.call_depth = 0 + + # Verify the value was set correctly + assert tracer._call_depth == 0 + + def test_call_depth_setter_negative_value_raises_value_error(self): + """Test setting call_depth with negative value raises ValueError.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Attempt to set negative call depth and verify it raises ValueError + with pytest.raises(ValueError) as exc_info: + tracer.call_depth = -1 + + # Verify the error message contains expected content + assert "call_depth cannot be negative" in str(exc_info.value) + assert "Received invalid value: -1" in str(exc_info.value) + assert "potential issue in the call stack tracking logic" in str(exc_info.value) + assert "Please report this issue to the developers" in str(exc_info.value) diff --git a/tests/test_coverup_25.py b/tests/test_coverup_25.py new file mode 100644 index 0000000..88dc14a --- /dev/null +++ b/tests/test_coverup_25.py @@ -0,0 +1,158 @@ +# file: objwatch/event_handls.py:78-104 +# asked: {"lines": [78, 80, 81, 82, 83, 84, 85, 86, 90, 91, 93, 94, 95, 97, 98, 100, 101, 102, 103, 104], "branches": [[93, 94], [93, 97], [100, 0], [100, 101]]} +# gained: {"lines": [78, 80, 81, 82, 83, 84, 85, 86, 90, 91, 93, 94, 95, 97, 98, 100, 101, 102, 103, 104], "branches": [[93, 94], [93, 97], [100, 0], [100, 101]]} + +import pytest +from unittest.mock import Mock, patch +import xml.etree.ElementTree as ET +from objwatch.event_handls import EventHandls +from objwatch.events import EventType + + +class TestEventHandlsHandleEnd: + + def test_handle_end_with_abc_wrapper_and_xml_output(self, monkeypatch): + """Test handle_end with abc_wrapper and XML output enabled.""" + # Setup + event_handls = EventHandls(output_xml="test.xml") + # Create real XML elements instead of mocks + root = ET.Element('root') + child = ET.SubElement(root, 'child') + event_handls.current_node = [root, child] + + func_info = {'qualified_name': 'test_function', 'symbol': 'test_symbol', 'symbol_type': 'method'} + + abc_wrapper = Mock() + abc_wrapper.wrap_return.return_value = "wrapped_result" + + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Execute + event_handls.handle_end( + lineno=100, + func_info=func_info, + abc_wrapper=abc_wrapper, + call_depth=2, + index_info="[1] ", + result="original_result", + ) + + # Assertions + abc_wrapper.wrap_return.assert_called_once_with('test_symbol', 'original_result') + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert "100" in call_args + assert "| | " in call_args + assert "[1]" in call_args + assert "end" in call_args + assert "test_function -> wrapped_result" in call_args + + # Check XML attributes on the child element (which should have been popped) + # The child element should have the attributes set before being popped + assert child.get('return_msg') == "wrapped_result" + assert child.get('end_line') == "100" + assert child.get('symbol_type') == "method" + + # Check that current_node was popped (should only contain root now) + assert len(event_handls.current_node) == 1 + assert event_handls.current_node[0] is root + + def test_handle_end_without_abc_wrapper_and_xml_output(self, monkeypatch): + """Test handle_end without abc_wrapper but with XML output enabled.""" + # Setup + event_handls = EventHandls(output_xml="test.xml") + root = ET.Element('root') + child = ET.SubElement(root, 'child') + event_handls.current_node = [root, child] + + func_info = {'qualified_name': 'test_function', 'symbol': 'test_symbol', 'symbol_type': 'function'} + + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Execute + event_handls.handle_end( + lineno=50, func_info=func_info, abc_wrapper=None, call_depth=1, index_info="[2] ", result="some_result" + ) + + # Assertions + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert "50" in call_args + assert "| " in call_args + assert "[2]" in call_args + assert "end" in call_args + assert "test_function" in call_args + assert "->" not in call_args # No return message without abc_wrapper + + # Check XML attributes + assert child.get('return_msg') == "" + assert child.get('end_line') == "50" + assert child.get('symbol_type') == "function" + + # Check that current_node was popped + assert len(event_handls.current_node) == 1 + assert event_handls.current_node[0] is root + + def test_handle_end_without_xml_output(self, monkeypatch): + """Test handle_end without XML output enabled.""" + # Setup + event_handls = EventHandls(output_xml=None) + + func_info = {'qualified_name': 'test_function', 'symbol': 'test_symbol', 'symbol_type': 'function'} + + abc_wrapper = Mock() + abc_wrapper.wrap_return.return_value = "wrapped_result" + + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Execute + event_handls.handle_end( + lineno=75, + func_info=func_info, + abc_wrapper=abc_wrapper, + call_depth=0, + index_info="", + result="original_result", + ) + + # Assertions + abc_wrapper.wrap_return.assert_called_once_with('test_symbol', 'original_result') + mock_log_debug.assert_called_once() + call_args = mock_log_debug.call_args[0][0] + assert "75" in call_args + assert "|" not in call_args # No call depth bars at depth 0 + assert "end" in call_args + assert "test_function -> wrapped_result" in call_args + + # Verify no XML operations were attempted + assert not hasattr(event_handls, 'current_node') or len(event_handls.current_node) <= 1 + + def test_handle_end_with_single_node_in_xml_stack(self, monkeypatch): + """Test handle_end when current_node has only one element (no pop).""" + # Setup + event_handls = EventHandls(output_xml="test.xml") + root = ET.Element('root') + event_handls.current_node = [root] # Only one element + + func_info = {'qualified_name': 'test_function', 'symbol': 'test_symbol', 'symbol_type': 'function'} + + mock_log_debug = Mock() + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + # Execute + event_handls.handle_end( + lineno=25, func_info=func_info, abc_wrapper=None, call_depth=3, index_info="[0] ", result="result" + ) + + # Assertions + mock_log_debug.assert_called_once() + + # Verify no attributes were set on root (since len(current_node) <= 1) + assert root.attrib == {} + + # Verify current_node still has only root + assert len(event_handls.current_node) == 1 + assert event_handls.current_node[0] is root diff --git a/tests/test_coverup_26.py b/tests/test_coverup_26.py new file mode 100644 index 0000000..a63824a --- /dev/null +++ b/tests/test_coverup_26.py @@ -0,0 +1,51 @@ +# file: objwatch/mp_handls.py:73-82 +# asked: {"lines": [73, 80, 81, 82], "branches": [[80, 81], [80, 82]]} +# gained: {"lines": [73, 80, 81, 82], "branches": [[80, 81], [80, 82]]} + +import pytest +from unittest.mock import Mock, patch +from objwatch.mp_handls import MPHandls + + +class TestMPHandlsIsInitialized: + """Test cases for MPHandls.is_initialized method to achieve full coverage.""" + + def test_is_initialized_framework_not_none_not_initialized_calls_check_initialized(self, monkeypatch): + """Test that _check_initialized is called when framework is not None and not initialized.""" + # Create instance with framework but not initialized + handler = MPHandls(framework='multiprocessing') + handler.initialized = False + + # Mock _check_initialized to track calls + mock_check_initialized = Mock() + monkeypatch.setattr(handler, '_check_initialized', mock_check_initialized) + + # Call is_initialized + result = handler.is_initialized() + + # Verify _check_initialized was called + mock_check_initialized.assert_called_once() + # Verify return value is False (since we didn't actually initialize) + assert result is False + + def test_is_initialized_framework_none_returns_current_state(self): + """Test that is_initialized returns current state when framework is None.""" + # Create instance with no framework + handler = MPHandls(framework=None) + + # Test when initialized is True + handler.initialized = True + assert handler.is_initialized() is True + + # Test when initialized is False + handler.initialized = False + assert handler.is_initialized() is False + + def test_is_initialized_already_initialized_returns_true(self): + """Test that is_initialized returns True when already initialized.""" + # Create instance that is already initialized + handler = MPHandls(framework='multiprocessing') + handler.initialized = True + + # Should return True without calling _check_initialized + assert handler.is_initialized() is True diff --git a/tests/test_coverup_27.py b/tests/test_coverup_27.py new file mode 100644 index 0000000..29f60fa --- /dev/null +++ b/tests/test_coverup_27.py @@ -0,0 +1,142 @@ +# file: objwatch/utils/weak.py:124-131 +# asked: {"lines": [124, 125, 126, 127, 128, 129, 130, 131], "branches": [[127, 128], [127, 131], [129, 127], [129, 130]]} +# gained: {"lines": [124, 125, 126, 127, 128, 129, 130, 131], "branches": [[127, 128], [127, 131], [129, 130]]} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, _IterationGuard + + +class TestWeakIdKeyDictionaryCopy: + def test_copy_with_valid_weak_references(self): + """Test copy method with valid weak references that survive the copy process.""" + # Create original dictionary + original = WeakIdKeyDictionary() + + # Create objects that support weak references + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + + original[obj1] = "value1" + original[obj2] = "value2" + + # Create a copy + copied = original.copy() + + # Verify the copy contains the same objects and values + assert len(copied) == 2 + assert copied[obj1] == "value1" + assert copied[obj2] == "value2" + + # Verify original is unchanged + assert len(original) == 2 + assert original[obj1] == "value1" + assert original[obj2] == "value2" + + # Verify they are separate instances + original[obj1] = "modified" + assert copied[obj1] == "value1" # Copy should not be affected + + def test_copy_with_expired_weak_references(self): + """Test copy method where some weak references have expired.""" + # Create original dictionary + original = WeakIdKeyDictionary() + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("persistent_object") + original[obj1] = "value1" + + # Create a weak reference that will be expired + def create_expired_reference(): + temp_obj = TestObject("temporary_object") + original[temp_obj] = "expired_value" + # temp_obj goes out of scope here and will be garbage collected + + create_expired_reference() + + # Force garbage collection to clean up the expired reference + import gc + + gc.collect() + + # Create a copy - should only copy the valid reference + copied = original.copy() + + # Verify only the valid object was copied + assert len(copied) == 1 + assert copied[obj1] == "value1" + # The expired reference should not be in the copy + + def test_copy_empty_dictionary(self): + """Test copy method on an empty dictionary.""" + original = WeakIdKeyDictionary() + copied = original.copy() + + assert len(copied) == 0 + assert isinstance(copied, WeakIdKeyDictionary) + assert copied is not original + + def test_copy_preserves_iteration_guard_context(self): + """Test that copy works correctly within iteration guard context.""" + original = WeakIdKeyDictionary() + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("guard_object_1") + obj2 = TestObject("guard_object_2") + original[obj1] = "value1" + original[obj2] = "value2" + + # Test that copy can be called while iterating (simulating the guard context) + with _IterationGuard(original): + copied = original.copy() + + # Verify the copy is correct + assert len(copied) == 2 + assert copied[obj1] == "value1" + assert copied[obj2] == "value2" + + def test_copy_with_mixed_valid_and_expired_references(self): + """Test copy with a mix of valid and expired weak references.""" + original = WeakIdKeyDictionary() + + class TestObject: + def __init__(self, name): + self.name = name + + # Add some valid references + valid_obj1 = TestObject("valid_object_1") + valid_obj2 = TestObject("valid_object_2") + original[valid_obj1] = "valid1" + original[valid_obj2] = "valid2" + + # Add an expired reference + def add_expired(): + expired_obj = TestObject("expired_object") + original[expired_obj] = "expired" + # expired_obj goes out of scope + + add_expired() + + # Force garbage collection + import gc + + gc.collect() + + # Create copy + copied = original.copy() + + # Verify only valid objects were copied + assert len(copied) == 2 + assert copied[valid_obj1] == "valid1" + assert copied[valid_obj2] == "valid2" + # The expired reference should not be in the copy diff --git a/tests/test_coverup_28.py b/tests/test_coverup_28.py new file mode 100644 index 0000000..83dacd9 --- /dev/null +++ b/tests/test_coverup_28.py @@ -0,0 +1,136 @@ +# file: objwatch/utils/weak.py:163-168 +# asked: {"lines": [163, 164, 165, 166, 167, 168], "branches": [[165, 0], [165, 166], [167, 165], [167, 168]]} +# gained: {"lines": [163, 164, 165, 166, 167, 168], "branches": [[165, 0], [165, 166], [167, 168]]} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, _IterationGuard + + +class TestWeakIdKeyDictionaryKeys: + def test_keys_iterates_over_valid_objects(self): + """Test that keys() yields only valid objects and handles iteration guard properly.""" + # Create a WeakIdKeyDictionary + weak_dict = WeakIdKeyDictionary() + + # Add some objects that can be weakly referenced (like custom classes) + class TestObj: + def __init__(self, name): + self.name = name + + obj1 = TestObj("obj1") + obj2 = TestObj("obj2") + obj3 = TestObj("obj3") + + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + weak_dict[obj3] = "value3" + + # Collect keys and verify they match our objects + keys = list(weak_dict.keys()) + + assert len(keys) == 3 + assert obj1 in keys + assert obj2 in keys + assert obj3 in keys + + # Verify iteration guard was properly managed + assert len(weak_dict._iterating) == 0 + + def test_keys_skips_collected_objects(self): + """Test that keys() skips objects that have been garbage collected.""" + weak_dict = WeakIdKeyDictionary() + + # Add an object and then let it be collected + class TestObj: + pass + + obj1 = TestObj() + weak_dict[obj1] = "value1" + + # Create a weak reference to track when obj1 is collected + obj1_ref = weakref.ref(obj1) + + # Force garbage collection to collect obj1 + del obj1 + import gc + + gc.collect() + + # Now obj1 should be collected, so keys() should yield nothing + keys = list(weak_dict.keys()) + + assert len(keys) == 0 + assert obj1_ref() is None + + # Verify iteration guard was properly managed + assert len(weak_dict._iterating) == 0 + + def test_keys_with_mixed_valid_and_collected_objects(self): + """Test that keys() properly handles a mix of valid and collected objects.""" + weak_dict = WeakIdKeyDictionary() + + # Add some objects + class TestObj: + def __init__(self, name): + self.name = name + + obj1 = TestObj("obj1") + obj2 = TestObj("obj2") + obj3 = TestObj("obj3") + + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + weak_dict[obj3] = "value3" + + # Create weak references + obj2_ref = weakref.ref(obj2) + + # Remove reference to obj2 and force collection + del obj2 + import gc + + gc.collect() + + # Now obj2 should be collected, but obj1 and obj3 should remain + keys = list(weak_dict.keys()) + + assert len(keys) == 2 + assert obj1 in keys + assert obj3 in keys + assert obj2_ref() is None + + # Verify iteration guard was properly managed + assert len(weak_dict._iterating) == 0 + + def test_keys_empty_dictionary(self): + """Test that keys() works correctly on an empty dictionary.""" + weak_dict = WeakIdKeyDictionary() + + keys = list(weak_dict.keys()) + + assert len(keys) == 0 + assert len(weak_dict._iterating) == 0 + + def test_keys_iteration_guard_context_manager(self): + """Test that the iteration guard properly manages the _iterating set.""" + weak_dict = WeakIdKeyDictionary() + + # Add an object + class TestObj: + pass + + obj = TestObj() + weak_dict[obj] = "value" + + # Manually check the iteration guard behavior + with _IterationGuard(weak_dict): + # During iteration, the guard should be in _iterating + assert len(weak_dict._iterating) == 1 + + # Call keys() which should use the iteration guard + keys = list(weak_dict.keys()) + assert keys == [obj] + + # After iteration, the guard should be removed + assert len(weak_dict._iterating) == 0 diff --git a/tests/test_coverup_29.py b/tests/test_coverup_29.py new file mode 100644 index 0000000..76268bd --- /dev/null +++ b/tests/test_coverup_29.py @@ -0,0 +1,55 @@ +# file: objwatch/utils/logger.py:69-82 +# asked: {"lines": [69, 79, 80, 82], "branches": [[79, 80], [79, 82]]} +# gained: {"lines": [69, 79, 80, 82], "branches": [[79, 80], [79, 82]]} + +import pytest +from unittest.mock import patch, MagicMock +from objwatch.utils.logger import log_info, FORCE + + +class TestLogInfo: + def test_log_info_with_force_enabled(self, monkeypatch): + """Test log_info when FORCE is True - should print to stdout""" + original_force = FORCE + monkeypatch.setattr('objwatch.utils.logger.FORCE', True) + + with patch('builtins.print') as mock_print: + test_msg = "Test message with FORCE enabled" + log_info(test_msg) + + mock_print.assert_called_once_with(test_msg, flush=True) + + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) + + def test_log_info_with_force_disabled(self, monkeypatch): + """Test log_info when FORCE is False - should use logger.info""" + original_force = FORCE + monkeypatch.setattr('objwatch.utils.logger.FORCE', False) + + with patch('objwatch.utils.logger.logger') as mock_logger: + test_msg = "Test message with FORCE disabled" + test_args = ("arg1", "arg2") + test_kwargs = {"key": "value"} + + log_info(test_msg, *test_args, **test_kwargs) + + mock_logger.info.assert_called_once_with(test_msg, *test_args, **test_kwargs) + + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) + + def test_log_info_with_args_and_kwargs_force_enabled(self, monkeypatch): + """Test log_info with args and kwargs when FORCE is True - should print only the message""" + original_force = FORCE + monkeypatch.setattr('objwatch.utils.logger.FORCE', True) + + with patch('builtins.print') as mock_print: + test_msg = "Test message with args" + test_args = ("arg1", 42) + test_kwargs = {"key1": "value1", "key2": 123} + + log_info(test_msg, *test_args, **test_kwargs) + + # When FORCE is True, only the message should be printed, args and kwargs are ignored + mock_print.assert_called_once_with(test_msg, flush=True) + + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) diff --git a/tests/test_coverup_3.py b/tests/test_coverup_3.py new file mode 100644 index 0000000..fd26433 --- /dev/null +++ b/tests/test_coverup_3.py @@ -0,0 +1,52 @@ +# file: objwatch/constants.py:13-47 +# asked: {"lines": [13, 14, 19, 22, 25, 29, 30, 31, 32, 33, 34, 35, 36, 41, 44, 47], "branches": []} +# gained: {"lines": [13, 14, 19, 22, 25, 29, 30, 31, 32, 33, 34, 35, 36, 41, 44, 47], "branches": []} + +import pytest +from objwatch.constants import Constants +from enum import Enum +from types import FunctionType + +try: + from types import NoneType +except ImportError: + NoneType = type(None) + + +class TestEnum(Enum): + TEST_VALUE = 1 + + +def test_constants_class_attributes(): + """Test that all Constants class attributes are accessible and have expected values.""" + assert Constants.MAX_TARGETS_DISPLAY == 8 + assert Constants.MAX_SEQUENCE_ELEMENTS == 3 + assert Constants.LOG_INDENT_LEVEL == 2 + assert Constants.HANDLE_GLOBALS_SYMBOL == "@" + assert Constants.HANDLE_LOCALS_SYMBOL == "_" + + +def test_log_element_types(): + """Test that LOG_ELEMENT_TYPES contains all expected types.""" + expected_types = (bool, int, float, str, NoneType, FunctionType, Enum) + assert Constants.LOG_ELEMENT_TYPES == expected_types + + # Test that each type in LOG_ELEMENT_TYPES is indeed a type + for element_type in Constants.LOG_ELEMENT_TYPES: + assert isinstance(element_type, type) + + +def test_log_sequence_types(): + """Test that LOG_SEQUENCE_TYPES contains all expected sequence types.""" + expected_types = (list, set, dict, tuple) + assert Constants.LOG_SEQUENCE_TYPES == expected_types + + # Test that each type in LOG_SEQUENCE_TYPES is indeed a type + for sequence_type in Constants.LOG_SEQUENCE_TYPES: + assert isinstance(sequence_type, type) + + +def test_constants_class_docstring(): + """Test that Constants class has the expected docstring.""" + expected_docstring = "Constants class for managing magic values and configuration parameters in ObjWatch project." + assert Constants.__doc__.strip() == expected_docstring diff --git a/tests/test_coverup_30.py b/tests/test_coverup_30.py new file mode 100644 index 0000000..192a65d --- /dev/null +++ b/tests/test_coverup_30.py @@ -0,0 +1,138 @@ +# file: objwatch/tracer.py:462-492 +# asked: {"lines": [462, 472, 473, 475, 476, 477, 478, 480, 481, 483, 484, 485, 486, 487, 488, 489, 492], "branches": [[475, 476], [475, 480]]} +# gained: {"lines": [462, 472, 473, 475, 476, 477, 478, 480, 481, 483, 484, 485, 486, 487, 488, 489, 492], "branches": [[475, 476], [475, 480]]} + +import pytest +import sys +from types import FrameType +from unittest.mock import Mock, patch +from objwatch.config import ObjWatchConfig + + +class TestTracerGetFunctionInfo: + """Test cases for Tracer._get_function_info method.""" + + def test_get_function_info_with_self_in_locals_method_traced(self, monkeypatch): + """Test _get_function_info when 'self' is in locals and method should be traced.""" + from objwatch.tracer import Tracer + + # Create config with required target + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock frame with 'self' in locals + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module'} + mock_frame.f_locals = {'self': Mock(__class__=Mock(__name__='TestClass'))} + mock_frame.f_code.co_name = 'test_method' + + # Mock _should_trace_method to return True + monkeypatch.setattr(tracer, '_should_trace_method', lambda module, cls, method: True) + + result = tracer._get_function_info(mock_frame) + + assert result['module'] == 'test_module' + assert result['symbol'] == 'TestClass.test_method' + assert result['symbol_type'] == 'method' + assert result['qualified_name'] == 'test_module.TestClass.test_method' + assert result['frame'] is mock_frame + + def test_get_function_info_with_self_in_locals_method_not_traced(self, monkeypatch): + """Test _get_function_info when 'self' is in locals but method should not be traced.""" + from objwatch.tracer import Tracer + + # Create config with required target + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock frame with 'self' in locals + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module'} + mock_frame.f_locals = {'self': Mock(__class__=Mock(__name__='TestClass'))} + mock_frame.f_code.co_name = 'test_method' + + # Mock _should_trace_method to return False + monkeypatch.setattr(tracer, '_should_trace_method', lambda module, cls, method: False) + + result = tracer._get_function_info(mock_frame) + + assert result['module'] == 'test_module' + assert result['symbol'] == 'TestClass.test_method' + assert result['symbol_type'] is None + assert result['qualified_name'] == 'test_module.TestClass.test_method' + assert result['frame'] is mock_frame + + def test_get_function_info_without_self_function_traced(self, monkeypatch): + """Test _get_function_info when 'self' is not in locals and function should be traced.""" + from objwatch.tracer import Tracer + + # Create config with required target + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock frame without 'self' in locals + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module'} + mock_frame.f_locals = {} + mock_frame.f_code.co_name = 'test_function' + + # Mock _should_trace_function to return True + monkeypatch.setattr(tracer, '_should_trace_function', lambda module, func: True) + + result = tracer._get_function_info(mock_frame) + + assert result['module'] == 'test_module' + assert result['symbol'] == 'test_function' + assert result['symbol_type'] == 'function' + assert result['qualified_name'] == 'test_module.test_function' + assert result['frame'] is mock_frame + + def test_get_function_info_without_self_function_not_traced(self, monkeypatch): + """Test _get_function_info when 'self' is not in locals and function should not be traced.""" + from objwatch.tracer import Tracer + + # Create config with required target + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock frame without 'self' in locals + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': 'test_module'} + mock_frame.f_locals = {} + mock_frame.f_code.co_name = 'test_function' + + # Mock _should_trace_function to return False + monkeypatch.setattr(tracer, '_should_trace_function', lambda module, func: False) + + result = tracer._get_function_info(mock_frame) + + assert result['module'] == 'test_module' + assert result['symbol'] == 'test_function' + assert result['symbol_type'] is None + assert result['qualified_name'] == 'test_module.test_function' + assert result['frame'] is mock_frame + + def test_get_function_info_with_empty_module_name(self, monkeypatch): + """Test _get_function_info when module name is empty.""" + from objwatch.tracer import Tracer + + # Create config with required target + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock frame with empty module name + mock_frame = Mock(spec=FrameType) + mock_frame.f_globals = {'__name__': ''} + mock_frame.f_locals = {} + mock_frame.f_code.co_name = 'test_function' + + # Mock _should_trace_function to return True + monkeypatch.setattr(tracer, '_should_trace_function', lambda module, func: True) + + result = tracer._get_function_info(mock_frame) + + assert result['module'] == '' + assert result['symbol'] == 'test_function' + assert result['symbol_type'] == 'function' + assert result['qualified_name'] == 'test_function' # No module prefix when module is empty + assert result['frame'] is mock_frame diff --git a/tests/test_coverup_31.py b/tests/test_coverup_31.py new file mode 100644 index 0000000..c47933a --- /dev/null +++ b/tests/test_coverup_31.py @@ -0,0 +1,75 @@ +# file: objwatch/utils/logger.py:85-98 +# asked: {"lines": [85, 95, 96, 98], "branches": [[95, 96], [95, 98]]} +# gained: {"lines": [85, 95, 96, 98], "branches": [[95, 96], [95, 98]]} + +import pytest +from unittest.mock import patch, MagicMock +from objwatch.utils.logger import log_debug, FORCE + + +class TestLogDebug: + """Test cases for log_debug function to achieve full coverage.""" + + def test_log_debug_with_force_enabled(self, monkeypatch): + """Test log_debug when FORCE is True - should print to stdout.""" + # Save original FORCE value + original_force = FORCE + + try: + # Set FORCE to True + monkeypatch.setattr('objwatch.utils.logger.FORCE', True) + + # Mock print function to capture output + with patch('builtins.print') as mock_print: + test_msg = "Test debug message with FORCE enabled" + log_debug(test_msg) + + # Verify print was called with correct arguments + mock_print.assert_called_once_with(test_msg, flush=True) + finally: + # Restore original FORCE value + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) + + def test_log_debug_with_force_disabled(self, monkeypatch): + """Test log_debug when FORCE is False - should use logger.debug.""" + # Save original FORCE value + original_force = FORCE + + try: + # Set FORCE to False + monkeypatch.setattr('objwatch.utils.logger.FORCE', False) + + # Mock logger.debug + with patch('objwatch.utils.logger.logger') as mock_logger: + test_msg = "Test debug message with FORCE disabled" + test_args = ("arg1", "arg2") + test_kwargs = {"key1": "value1", "key2": "value2"} + + log_debug(test_msg, *test_args, **test_kwargs) + + # Verify logger.debug was called with correct arguments + mock_logger.debug.assert_called_once_with(test_msg, *test_args, **test_kwargs) + finally: + # Restore original FORCE value + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) + + def test_log_debug_with_force_disabled_no_args(self, monkeypatch): + """Test log_debug when FORCE is False with no additional arguments.""" + # Save original FORCE value + original_force = FORCE + + try: + # Set FORCE to False + monkeypatch.setattr('objwatch.utils.logger.FORCE', False) + + # Mock logger.debug + with patch('objwatch.utils.logger.logger') as mock_logger: + test_msg = "Simple debug message" + + log_debug(test_msg) + + # Verify logger.debug was called with correct arguments + mock_logger.debug.assert_called_once_with(test_msg) + finally: + # Restore original FORCE value + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) diff --git a/tests/test_coverup_32.py b/tests/test_coverup_32.py new file mode 100644 index 0000000..6d028f4 --- /dev/null +++ b/tests/test_coverup_32.py @@ -0,0 +1,232 @@ +# file: objwatch/tracer.py:283-302 +# asked: {"lines": [283, 284, 296, 297, 299, 300, 302], "branches": [[297, 299], [297, 302]]} +# gained: {"lines": [283, 284, 296, 297, 299, 300, 302], "branches": [[297, 299], [297, 302]]} + +import pytest +from unittest.mock import Mock, MagicMock +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerShouldTraceMethod: + """Test cases for Tracer._should_trace_method method to achieve full coverage.""" + + def test_should_trace_method_track_all_true_method_not_excluded(self, monkeypatch): + """Test when track_all is True and method is not in excluded methods.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_method_index = {'test_module': {'TestClass': {'excluded_method'}}} + tracer.method_index = {} + + # Test: method not in excluded methods + result = tracer._should_trace_method('test_module', 'TestClass', 'some_method') + assert result is True + + # Verify cache is working by calling again + result2 = tracer._should_trace_method('test_module', 'TestClass', 'some_method') + assert result2 is True + + def test_should_trace_method_track_all_true_method_excluded(self, monkeypatch): + """Test when track_all is True and method is in excluded methods.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_method_index = {'test_module': {'TestClass': {'excluded_method'}}} + tracer.method_index = {} + + # Test: method is in excluded methods + result = tracer._should_trace_method('test_module', 'TestClass', 'excluded_method') + assert result is False + + def test_should_trace_method_track_all_false_method_in_index(self, monkeypatch): + """Test when track_all is False and method is in method_index.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'TestClass': {'track_all': False}}} + tracer.exclude_method_index = {} + tracer.method_index = {'test_module': {'TestClass': {'target_method'}}} + + # Test: method is in method_index + result = tracer._should_trace_method('test_module', 'TestClass', 'target_method') + assert result is True + + def test_should_trace_method_track_all_false_method_not_in_index(self, monkeypatch): + """Test when track_all is False and method is not in method_index.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'TestClass': {'track_all': False}}} + tracer.exclude_method_index = {} + tracer.method_index = {'test_module': {'TestClass': {'other_method'}}} + + # Test: method is not in method_index + result = tracer._should_trace_method('test_module', 'TestClass', 'some_method') + assert result is False + + def test_should_trace_method_module_not_in_class_info(self, monkeypatch): + """Test when module is not present in class_info.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {} + tracer.exclude_method_index = {} + tracer.method_index = {} + + # Test: module not in class_info + result = tracer._should_trace_method('unknown_module', 'TestClass', 'some_method') + assert result is False + + def test_should_trace_method_class_not_in_class_info(self, monkeypatch): + """Test when class is not present in class_info for the module.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'OtherClass': {'track_all': True}}} + tracer.exclude_method_index = {} + tracer.method_index = {} + + # Test: class not in class_info for the module + result = tracer._should_trace_method('test_module', 'UnknownClass', 'some_method') + assert result is False + + def test_should_trace_method_track_all_true_empty_exclude_method_index(self, monkeypatch): + """Test when track_all is True and exclude_method_index is empty for the module/class.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_method_index = {} + tracer.method_index = {} + + # Test: track_all True with empty exclude_method_index + result = tracer._should_trace_method('test_module', 'TestClass', 'some_method') + assert result is True + + def test_should_trace_method_track_all_true_module_not_in_exclude_method_index(self, monkeypatch): + """Test when track_all is True and module is not in exclude_method_index.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_method_index = {'other_module': {'TestClass': {'excluded_method'}}} + tracer.method_index = {} + + # Test: track_all True with module not in exclude_method_index + result = tracer._should_trace_method('test_module', 'TestClass', 'some_method') + assert result is True + + def test_should_trace_method_track_all_true_class_not_in_exclude_method_index(self, monkeypatch): + """Test when track_all is True and class is not in exclude_method_index for the module.""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = {} + mock_config.exclude_targets = {} + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + tracer = Tracer(mock_config) + + # Set up the required attributes + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_method_index = {'test_module': {'OtherClass': {'excluded_method'}}} + tracer.method_index = {} + + # Test: track_all True with class not in exclude_method_index for the module + result = tracer._should_trace_method('test_module', 'TestClass', 'some_method') + assert result is True diff --git a/tests/test_coverup_33.py b/tests/test_coverup_33.py new file mode 100644 index 0000000..ef5f86f --- /dev/null +++ b/tests/test_coverup_33.py @@ -0,0 +1,75 @@ +# file: objwatch/utils/logger.py:101-114 +# asked: {"lines": [101, 111, 112, 114], "branches": [[111, 112], [111, 114]]} +# gained: {"lines": [101, 111, 112, 114], "branches": [[111, 112], [111, 114]]} + +import pytest +from unittest.mock import patch, MagicMock +import objwatch.utils.logger as logger_module + + +class TestLogWarn: + """Test cases for log_warn function to achieve full coverage.""" + + def test_log_warn_with_force_true(self, monkeypatch): + """Test log_warn when FORCE is True - should print to stdout.""" + # Save original FORCE value + original_force = logger_module.FORCE + + try: + # Set FORCE to True + monkeypatch.setattr(logger_module, 'FORCE', True) + + # Mock print to capture output + with patch('builtins.print') as mock_print: + test_msg = "Test warning message with FORCE=True" + logger_module.log_warn(test_msg) + + # Verify print was called with correct arguments + mock_print.assert_called_once_with(test_msg, flush=True) + finally: + # Restore original FORCE value + monkeypatch.setattr(logger_module, 'FORCE', original_force) + + def test_log_warn_with_force_false(self, monkeypatch): + """Test log_warn when FORCE is False - should use logger.warning.""" + # Save original FORCE value + original_force = logger_module.FORCE + + try: + # Set FORCE to False + monkeypatch.setattr(logger_module, 'FORCE', False) + + # Mock logger.warning to verify it's called + with patch.object(logger_module.logger, 'warning') as mock_warning: + test_msg = "Test warning message with FORCE=False" + test_args = ("arg1", "arg2") + test_kwargs = {"key": "value"} + + logger_module.log_warn(test_msg, *test_args, **test_kwargs) + + # Verify logger.warning was called with correct arguments + mock_warning.assert_called_once_with(test_msg, *test_args, **test_kwargs) + finally: + # Restore original FORCE value + monkeypatch.setattr(logger_module, 'FORCE', original_force) + + def test_log_warn_with_force_false_no_args(self, monkeypatch): + """Test log_warn when FORCE is False with no additional arguments.""" + # Save original FORCE value + original_force = logger_module.FORCE + + try: + # Set FORCE to False + monkeypatch.setattr(logger_module, 'FORCE', False) + + # Mock logger.warning to verify it's called + with patch.object(logger_module.logger, 'warning') as mock_warning: + test_msg = "Simple warning message" + + logger_module.log_warn(test_msg) + + # Verify logger.warning was called with correct arguments + mock_warning.assert_called_once_with(test_msg) + finally: + # Restore original FORCE value + monkeypatch.setattr(logger_module, 'FORCE', original_force) diff --git a/tests/test_coverup_34.py b/tests/test_coverup_34.py new file mode 100644 index 0000000..a17aefb --- /dev/null +++ b/tests/test_coverup_34.py @@ -0,0 +1,190 @@ +# file: objwatch/targets.py:336-373 +# asked: {"lines": [336, 348, 350, 351, 352, 354, 356, 357, 358, 359, 360, 361, 363, 364, 365, 366, 367, 368, 370, 371, 373], "branches": [[356, 357], [356, 373], [357, 358], [357, 364], [364, 365], [364, 367], [365, 356], [365, 366], [367, 356], [367, 368]]} +# gained: {"lines": [336, 348, 350, 351, 352, 354, 356, 357, 358, 359, 360, 361, 363, 364, 365, 366, 367, 368, 370, 371, 373], "branches": [[356, 357], [356, 373], [357, 358], [357, 364], [364, 365], [364, 367], [365, 356], [365, 366], [367, 356], [367, 368]]} + +import pytest +import ast +import tempfile +import os +from unittest.mock import patch, MagicMock +from objwatch.targets import Targets +from objwatch.utils.logger import log_error + + +class TestTargetsParsePyFile: + """Test cases for Targets._parse_py_file method to achieve full coverage.""" + + def test_parse_py_file_successful_parsing(self, monkeypatch): + """Test successful parsing of a Python file with various AST elements.""" + targets = Targets(targets=[]) + + # Create a temporary Python file with various AST elements + test_code = ''' +class TestClass: + def method1(self): + pass + + def method2(self): + pass + +def standalone_function(): + pass + +global_var = 42 +a, b = 1, 2 +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(test_code) + temp_file = f.name + + try: + result = targets._parse_py_file(temp_file) + + # Verify class parsing + assert 'TestClass' in result['classes'] + assert result['classes']['TestClass']['methods'] == [] + assert result['classes']['TestClass']['attributes'] == [] + assert result['classes']['TestClass']['track_all'] is True + + # Verify function parsing + assert 'standalone_function' in result['functions'] + + # Verify global variable parsing + assert 'global_var' in result['globals'] + assert 'a' in result['globals'] + assert 'b' in result['globals'] + + finally: + os.unlink(temp_file) + + def test_parse_py_file_parsing_failure(self, monkeypatch): + """Test parsing failure when file contains invalid Python syntax.""" + targets = Targets(targets=[]) + + # Create a temporary file with invalid Python syntax + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write('def invalid syntax here') + temp_file = f.name + + try: + # Mock log_error to verify it's called + mock_log_error = MagicMock() + monkeypatch.setattr('objwatch.targets.log_error', mock_log_error) + + result = targets._parse_py_file(temp_file) + + # Verify error was logged + mock_log_error.assert_called_once() + error_msg = mock_log_error.call_args[0][0] + assert 'Failed to parse' in error_msg + assert temp_file in error_msg + + # Verify empty structure is returned on error + assert result == {'classes': {}, 'functions': [], 'globals': []} + + finally: + os.unlink(temp_file) + + def test_parse_py_file_nested_functions(self, monkeypatch): + """Test parsing of nested functions (should not be included in functions list).""" + targets = Targets(targets=[]) + + test_code = ''' +def outer_function(): + pass + +class MyClass: + def class_method(self): + pass +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(test_code) + temp_file = f.name + + try: + result = targets._parse_py_file(temp_file) + + # Only outer_function should be in functions list + assert result['functions'] == ['outer_function'] + + # MyClass should be in classes + assert 'MyClass' in result['classes'] + + finally: + os.unlink(temp_file) + + def test_parse_py_file_class_attributes_and_methods(self, monkeypatch): + """Test parsing of classes with attributes and methods.""" + targets = Targets(targets=[]) + + test_code = ''' +class ComplexClass: + class_attr = "value" + + def __init__(self): + self.instance_attr = 42 + + def method1(self): + pass + + @classmethod + def class_method(cls): + pass + + @staticmethod + def static_method(): + pass +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(test_code) + temp_file = f.name + + try: + result = targets._parse_py_file(temp_file) + + # Verify class is parsed + assert 'ComplexClass' in result['classes'] + class_info = result['classes']['ComplexClass'] + + # Note: The current implementation doesn't populate methods and attributes lists + # but sets track_all to True + assert class_info['track_all'] is True + + finally: + os.unlink(temp_file) + + def test_parse_py_file_empty_file(self, monkeypatch): + """Test parsing of an empty Python file.""" + targets = Targets(targets=[]) + + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write('') + temp_file = f.name + + try: + result = targets._parse_py_file(temp_file) + + # Should return empty structure + assert result == {'classes': {}, 'functions': [], 'globals': []} + + finally: + os.unlink(temp_file) + + def test_parse_py_file_file_not_found(self, monkeypatch): + """Test behavior when file doesn't exist.""" + targets = Targets(targets=[]) + + # Mock log_error to verify it's called + mock_log_error = MagicMock() + monkeypatch.setattr('objwatch.targets.log_error', mock_log_error) + + result = targets._parse_py_file('/nonexistent/path/file.py') + + # Verify error was logged + mock_log_error.assert_called_once() + error_msg = mock_log_error.call_args[0][0] + assert 'Failed to parse' in error_msg + assert '/nonexistent/path/file.py' in error_msg + + # Verify empty structure is returned on error + assert result == {'classes': {}, 'functions': [], 'globals': []} diff --git a/tests/test_coverup_35.py b/tests/test_coverup_35.py new file mode 100644 index 0000000..87e0b57 --- /dev/null +++ b/tests/test_coverup_35.py @@ -0,0 +1,73 @@ +# file: objwatch/utils/logger.py:117-130 +# asked: {"lines": [117, 127, 128, 130], "branches": [[127, 128], [127, 130]]} +# gained: {"lines": [117, 127, 128, 130], "branches": [[127, 128], [127, 130]]} + +import pytest +from unittest.mock import patch, MagicMock +from objwatch.utils.logger import log_error, FORCE + + +class TestLogError: + def test_log_error_with_force_enabled(self, monkeypatch): + """Test log_error when FORCE is True - should print to stdout""" + # Save original FORCE value + original_force = FORCE + + try: + # Set FORCE to True + monkeypatch.setattr('objwatch.utils.logger.FORCE', True) + + # Mock print to capture output + with patch('builtins.print') as mock_print: + test_msg = "Test error message" + log_error(test_msg) + + # Verify print was called with the message and flush=True + mock_print.assert_called_once_with(test_msg, flush=True) + finally: + # Restore original FORCE value + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) + + def test_log_error_with_force_disabled(self, monkeypatch): + """Test log_error when FORCE is False - should use logger.error""" + # Save original FORCE value + original_force = FORCE + + try: + # Set FORCE to False + monkeypatch.setattr('objwatch.utils.logger.FORCE', False) + + # Mock logger.error + with patch('objwatch.utils.logger.logger') as mock_logger: + test_msg = "Test error message" + test_args = ("arg1", "arg2") + test_kwargs = {"key1": "value1", "key2": "value2"} + + log_error(test_msg, *test_args, **test_kwargs) + + # Verify logger.error was called with all arguments + mock_logger.error.assert_called_once_with(test_msg, *test_args, **test_kwargs) + finally: + # Restore original FORCE value + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) + + def test_log_error_with_force_disabled_no_extra_args(self, monkeypatch): + """Test log_error when FORCE is False with no extra arguments""" + # Save original FORCE value + original_force = FORCE + + try: + # Set FORCE to False + monkeypatch.setattr('objwatch.utils.logger.FORCE', False) + + # Mock logger.error + with patch('objwatch.utils.logger.logger') as mock_logger: + test_msg = "Simple error message" + + log_error(test_msg) + + # Verify logger.error was called with just the message + mock_logger.error.assert_called_once_with(test_msg) + finally: + # Restore original FORCE value + monkeypatch.setattr('objwatch.utils.logger.FORCE', original_force) diff --git a/tests/test_coverup_36.py b/tests/test_coverup_36.py new file mode 100644 index 0000000..e198c08 --- /dev/null +++ b/tests/test_coverup_36.py @@ -0,0 +1,192 @@ +# file: objwatch/targets.py:156-201 +# asked: {"lines": [156, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 197, 198, 199, 200, 201], "branches": [[166, 167], [166, 180], [180, 181], [180, 197], [183, 184], [183, 197], [185, 186], [185, 197]]} +# gained: {"lines": [156, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 197, 198, 199, 200, 201], "branches": [[166, 167], [166, 180], [180, 181], [180, 197], [183, 184], [183, 197], [185, 186]]} + +import pytest +import inspect +from types import MethodType, FunctionType +from objwatch.targets import Targets + + +class TestClass: + def instance_method(self): + pass + + @classmethod + def class_method(cls): + pass + + @staticmethod + def static_method(): + pass + + +def regular_function(): + pass + + +class TestTargetsParseFunction: + def test_parse_class_method(self): + """Test parsing a class method (bound to class)""" + targets = Targets([]) + result = targets._parse_function(TestClass.class_method) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + assert module_name == __name__ + assert structure == { + 'classes': {'TestClass': {'methods': ['class_method'], 'attributes': [], 'track_all': False}}, + 'functions': [], + 'globals': [], + } + + def test_parse_static_method_via_qualname(self): + """Test parsing a static method using qualname path""" + targets = Targets([]) + result = targets._parse_function(TestClass.static_method) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + assert module_name == __name__ + assert structure == { + 'classes': {'TestClass': {'methods': ['static_method'], 'attributes': [], 'track_all': False}}, + 'functions': [], + 'globals': [], + } + + def test_parse_regular_function(self): + """Test parsing a regular function""" + targets = Targets([]) + result = targets._parse_function(regular_function) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + assert module_name == __name__ + assert structure == {'classes': {}, 'functions': ['regular_function'], 'globals': []} + + def test_parse_function_without_module(self, monkeypatch): + """Test parsing a function without a module""" + targets = Targets([]) + + # Create a function without a module + func = lambda x: x + + # Mock inspect.getmodule to return None + monkeypatch.setattr(inspect, 'getmodule', lambda x: None) + + result = targets._parse_function(func) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + assert module_name == '' + assert structure == {'classes': {}, 'functions': [func.__name__], 'globals': []} + + def test_parse_class_method_without_module(self, monkeypatch): + """Test parsing a class method without a module""" + targets = Targets([]) + + # Mock inspect.getmodule to return None for class + original_getmodule = inspect.getmodule + + def mock_getmodule(obj): + if obj == TestClass: + return None + return original_getmodule(obj) + + monkeypatch.setattr(inspect, 'getmodule', mock_getmodule) + + result = targets._parse_function(TestClass.class_method) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + assert module_name == '' + assert structure == { + 'classes': {'TestClass': {'methods': ['class_method'], 'attributes': [], 'track_all': False}}, + 'functions': [], + 'globals': [], + } + + def test_parse_method_via_qualname_without_module(self, monkeypatch): + """Test parsing a method via qualname without module""" + targets = Targets([]) + + # Create a mock function with qualname but no module + class MockFunc: + __qualname__ = 'SomeClass.some_method' + __name__ = 'some_method' + + func = MockFunc() + + # Mock inspect.getmodule to return None + monkeypatch.setattr(inspect, 'getmodule', lambda x: None) + + result = targets._parse_function(func) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + assert module_name == '' + assert structure == {'classes': {}, 'functions': ['some_method'], 'globals': []} + + def test_parse_method_via_qualname_module_no_class(self, monkeypatch): + """Test parsing a method via qualname where module exists but class doesn't""" + targets = Targets([]) + + # Create a mock function with qualname + class MockFunc: + __qualname__ = 'NonExistentClass.some_method' + __name__ = 'some_method' + + func = MockFunc() + + # Mock inspect.getmodule to return a mock module with __name__ attribute + mock_module = type('MockModule', (), {'__name__': 'test_module'})() + + monkeypatch.setattr(inspect, 'getmodule', lambda x: mock_module) + + result = targets._parse_function(func) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + # Should fall back to regular function handling + assert module_name == 'test_module' + assert structure == {'classes': {}, 'functions': ['some_method'], 'globals': []} + + def test_parse_method_via_qualname_module_class_not_type(self, monkeypatch): + """Test parsing a method via qualname where class exists but is not a type""" + targets = Targets([]) + + # Create a mock function with qualname + class MockFunc: + __qualname__ = 'some_attr.some_method' + __name__ = 'some_method' + + func = MockFunc() + + # Mock module with a non-type attribute and __name__ + mock_module = type('MockModule', (), {'__name__': 'test_module', 'some_attr': 'not_a_class'})() + + monkeypatch.setattr(inspect, 'getmodule', lambda x: mock_module) + + result = targets._parse_function(func) + + assert isinstance(result, tuple) + assert len(result) == 2 + module_name, structure = result + + # Should fall back to regular function handling + assert module_name == 'test_module' + assert structure == {'classes': {}, 'functions': ['some_method'], 'globals': []} diff --git a/tests/test_coverup_37.py b/tests/test_coverup_37.py new file mode 100644 index 0000000..8519570 --- /dev/null +++ b/tests/test_coverup_37.py @@ -0,0 +1,145 @@ +# file: objwatch/utils/weak.py:172-176 +# asked: {"lines": [172, 173, 174, 175, 176], "branches": [[174, 0], [174, 175], [175, 174], [175, 176]]} +# gained: {"lines": [172, 173, 174, 175, 176], "branches": [[174, 0], [174, 175], [175, 176]]} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, _IterationGuard + + +class TestWeakIdKeyDictionaryValues: + def test_values_with_live_objects(self): + """Test that values() yields values for live objects only.""" + + # Use objects that can be weakly referenced + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + obj3 = TestObject("obj3") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + weak_dict[obj3] = "value3" + + # Collect values from the generator + values_list = list(weak_dict.values()) + + # All values should be present since objects are still alive + assert len(values_list) == 3 + assert "value1" in values_list + assert "value2" in values_list + assert "value3" in values_list + + def test_values_with_dead_objects(self): + """Test that values() skips values for dead objects.""" + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + + # Create a reference to obj2 and then delete it + obj2_ref = weakref.ref(obj2) + del obj2 + + # Force garbage collection to clean up obj2 + import gc + + gc.collect() + + # obj2 should be dead now + assert obj2_ref() is None + + # Collect values from the generator + values_list = list(weak_dict.values()) + + # Only value1 should be present since obj2 is dead + assert len(values_list) == 1 + assert "value1" in values_list + assert "value2" not in values_list + + def test_values_empty_dict(self): + """Test that values() works correctly with an empty dictionary.""" + weak_dict = WeakIdKeyDictionary() + + # Collect values from the generator + values_list = list(weak_dict.values()) + + # Should be empty + assert len(values_list) == 0 + + def test_values_iteration_guard_context(self, monkeypatch): + """Test that values() properly uses _IterationGuard context manager.""" + weak_dict = WeakIdKeyDictionary() + + # Track if _IterationGuard was used + iteration_guard_used = False + + def mock_iteration_guard_init(self, container): + nonlocal iteration_guard_used + iteration_guard_used = True + + def mock_iteration_guard_enter(self): + return self + + def mock_iteration_guard_exit(self, exc_type, exc_val, exc_tb): + pass + + # Monkeypatch _IterationGuard + monkeypatch.setattr('objwatch.utils.weak._IterationGuard.__init__', mock_iteration_guard_init) + monkeypatch.setattr('objwatch.utils.weak._IterationGuard.__enter__', mock_iteration_guard_enter) + monkeypatch.setattr('objwatch.utils.weak._IterationGuard.__exit__', mock_iteration_guard_exit) + + # Call values() and consume the generator + values_gen = weak_dict.values() + list(values_gen) + + # Verify _IterationGuard was used + assert iteration_guard_used + + def test_values_mixed_live_dead_objects(self): + """Test values() with a mix of live and dead objects.""" + + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + obj3 = TestObject("obj3") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + weak_dict[obj3] = "value3" + + # Create references and delete obj2 + obj2_ref = weakref.ref(obj2) + del obj2 + + # Force garbage collection + import gc + + gc.collect() + + # Verify obj2 is dead + assert obj2_ref() is None + + # Collect values + values_list = list(weak_dict.values()) + + # Should only contain values for live objects + assert len(values_list) == 2 + assert "value1" in values_list + assert "value3" in values_list + assert "value2" not in values_list diff --git a/tests/test_coverup_39.py b/tests/test_coverup_39.py new file mode 100644 index 0000000..32bf402 --- /dev/null +++ b/tests/test_coverup_39.py @@ -0,0 +1,100 @@ +# file: objwatch/mp_handls.py:84-95 +# asked: {"lines": [84, 89, 91, 92, 93, 94, 95], "branches": [[91, 0], [91, 92]]} +# gained: {"lines": [84, 89, 91, 92, 93, 94, 95], "branches": [[91, 0], [91, 92]]} + +import pytest +from unittest.mock import patch, MagicMock + + +class TestMPHandls: + """Test cases for MPHandls class _check_init_torch method""" + + def test_check_init_torch_initialized(self): + """Test _check_init_torch when torch.distributed is initialized""" + from objwatch.mp_handls import MPHandls + + # Mock torch.distributed.is_initialized to return True + mock_dist = MagicMock() + mock_dist.is_initialized.return_value = True + mock_dist.get_rank.return_value = 0 + + # Mock torch.distributed module + mock_torch = MagicMock() + mock_torch.distributed = mock_dist + + # Mock log_info to capture the log message + mock_log_info = MagicMock() + + with patch('objwatch.mp_handls.log_info', mock_log_info): + with patch.dict('sys.modules', {'torch': mock_torch}): + # Create MPHandls instance + handler = MPHandls() + + # Call the method directly + handler._check_init_torch() + + # Verify the state was set correctly + assert handler.initialized == True + assert handler.index == 0 + assert handler.sync_fn == mock_dist.barrier + + # Verify the log message + mock_log_info.assert_called_once_with("torch.distributed initialized. index: 0") + + def test_check_init_torch_not_initialized(self): + """Test _check_init_torch when torch.distributed is not initialized""" + from objwatch.mp_handls import MPHandls + + # Mock torch.distributed.is_initialized to return False + mock_dist = MagicMock() + mock_dist.is_initialized.return_value = False + + # Mock torch.distributed module + mock_torch = MagicMock() + mock_torch.distributed = mock_dist + + # Mock log_info to ensure it's not called + mock_log_info = MagicMock() + + with patch('objwatch.mp_handls.log_info', mock_log_info): + with patch.dict('sys.modules', {'torch': mock_torch}): + # Create MPHandls instance + handler = MPHandls() + + # Call the method directly + handler._check_init_torch() + + # Verify the state was not changed + assert handler.initialized == False + assert handler.index is None + assert handler.sync_fn is None + + # Verify log_info was not called + mock_log_info.assert_not_called() + + def test_check_init_torch_no_distributed(self): + """Test _check_init_torch when torch.distributed is None""" + from objwatch.mp_handls import MPHandls + + # Mock torch without distributed module + mock_torch = MagicMock() + mock_torch.distributed = None + + # Mock log_info to ensure it's not called + mock_log_info = MagicMock() + + with patch('objwatch.mp_handls.log_info', mock_log_info): + with patch.dict('sys.modules', {'torch': mock_torch}): + # Create MPHandls instance + handler = MPHandls() + + # Call the method directly + handler._check_init_torch() + + # Verify the state was not changed + assert handler.initialized == False + assert handler.index is None + assert handler.sync_fn is None + + # Verify log_info was not called + mock_log_info.assert_not_called() diff --git a/tests/test_coverup_4.py b/tests/test_coverup_4.py new file mode 100644 index 0000000..019f841 --- /dev/null +++ b/tests/test_coverup_4.py @@ -0,0 +1,96 @@ +# file: objwatch/mp_handls.py:35-54 +# asked: {"lines": [35, 40, 41, 42, 43, 44, 45, 48, 49, 50, 51, 53, 54], "branches": [[40, 41], [40, 42], [42, 43], [42, 44], [44, 45], [44, 48], [49, 50], [49, 53]]} +# gained: {"lines": [35, 40, 41, 42, 43, 44, 45, 48, 49, 50, 51, 53, 54], "branches": [[40, 41], [40, 42], [42, 43], [42, 44], [44, 45], [44, 48], [49, 50], [49, 53]]} + +import pytest +from unittest.mock import Mock, patch, MagicMock +from objwatch.mp_handls import MPHandls +from objwatch.utils.logger import log_error + + +class TestMPHandlsCheckInitialized: + """Test cases for MPHandls._check_initialized method""" + + def test_check_initialized_framework_none(self): + """Test when framework is None - should pass without doing anything""" + mp_handls = MPHandls() + mp_handls.framework = None + + # This should not raise any exception and not call any methods + mp_handls._check_initialized() + + # Verify no initialization occurred + assert not hasattr(mp_handls, 'initialized') or not mp_handls.initialized + + def test_check_initialized_torch_distributed(self, monkeypatch): + """Test when framework is 'torch.distributed'""" + mp_handls = MPHandls() + mp_handls.framework = 'torch.distributed' + + # Mock the _check_init_torch method + mock_check_torch = Mock() + mp_handls._check_init_torch = mock_check_torch + + mp_handls._check_initialized() + + # Verify _check_init_torch was called + mock_check_torch.assert_called_once() + + def test_check_initialized_multiprocessing(self, monkeypatch): + """Test when framework is 'multiprocessing'""" + mp_handls = MPHandls() + mp_handls.framework = 'multiprocessing' + + # Mock the _check_init_multiprocessing method + mock_check_mp = Mock() + mp_handls._check_init_multiprocessing = mock_check_mp + + mp_handls._check_initialized() + + # Verify _check_init_multiprocessing was called + mock_check_mp.assert_called_once() + + def test_check_initialized_custom_framework_valid(self, monkeypatch): + """Test when framework is a custom framework with valid method""" + mp_handls = MPHandls() + mp_handls.framework = 'custom_framework' + + # Create a mock custom method + mock_custom_method = Mock() + + # Set the custom method on the instance + mp_handls._check_init_custom_framework = mock_custom_method + + mp_handls._check_initialized() + + # Verify the custom method was called + mock_custom_method.assert_called_once() + + def test_check_initialized_custom_framework_invalid(self, monkeypatch): + """Test when framework is a custom framework without valid method""" + mp_handls = MPHandls() + mp_handls.framework = 'invalid_framework' + + # Mock log_error to verify it's called + with patch('objwatch.mp_handls.log_error') as mock_log_error: + with pytest.raises(ValueError, match="Invalid framework: invalid_framework"): + mp_handls._check_initialized() + + # Verify log_error was called with the correct message + mock_log_error.assert_called_once_with("Invalid framework: invalid_framework") + + def test_check_initialized_custom_framework_hasattr_false(self, monkeypatch): + """Test when hasattr returns False for custom framework method""" + mp_handls = MPHandls() + mp_handls.framework = 'another_invalid' + + # Ensure the custom method doesn't exist + assert not hasattr(mp_handls, '_check_init_another_invalid') + + # Mock log_error to verify it's called + with patch('objwatch.mp_handls.log_error') as mock_log_error: + with pytest.raises(ValueError, match="Invalid framework: another_invalid"): + mp_handls._check_initialized() + + # Verify log_error was called with the correct message + mock_log_error.assert_called_once_with("Invalid framework: another_invalid") diff --git a/tests/test_coverup_40.py b/tests/test_coverup_40.py new file mode 100644 index 0000000..d751658 --- /dev/null +++ b/tests/test_coverup_40.py @@ -0,0 +1,257 @@ +# file: objwatch/tracer.py:604-652 +# asked: {"lines": [604, 612, 613, 615, 616, 617, 619, 620, 621, 623, 624, 625, 626, 627, 628, 629, 630, 631, 634, 635, 637, 638, 639, 640, 641, 642, 643, 645, 646, 649, 650, 652], "branches": [[612, 613], [612, 615], [620, 621], [620, 637], [634, 620], [634, 635], [638, 639], [638, 652], [649, 638], [649, 650]]} +# gained: {"lines": [604, 612, 613, 615, 616, 617, 619, 620, 621, 623, 624, 625, 626, 627, 628, 629, 630, 631, 634, 635, 637, 638, 639, 640, 641, 642, 643, 645, 646, 649, 650, 652], "branches": [[612, 613], [612, 615], [620, 621], [620, 637], [634, 620], [634, 635], [638, 639], [638, 652], [649, 638], [649, 650]]} + +import pytest +from types import FrameType +from unittest.mock import Mock, MagicMock, patch +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig +from objwatch.constants import Constants + + +class TestTracerTrackLocalsChange: + """Test cases for Tracer._track_locals_change method to achieve full coverage.""" + + def test_track_locals_change_with_locals_disabled(self, monkeypatch): + """Test that method returns early when with_locals is False.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=False) + tracer = Tracer(config) + frame = Mock(spec=FrameType) + lineno = 10 + + # Mock event_handlers to ensure it's not called + mock_event_handlers = Mock() + tracer.event_handlers = mock_event_handlers + + tracer._track_locals_change(frame, lineno) + + # Verify no interactions with event_handlers + mock_event_handlers.handle_upd.assert_not_called() + + def test_track_locals_change_frame_not_tracked(self, monkeypatch): + """Test that method returns early when frame is not in tracked_locals.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True) + tracer = Tracer(config) + frame = Mock(spec=FrameType) + lineno = 10 + + # Ensure frame is not in tracked_locals + tracer.tracked_locals = {} + + # Mock event_handlers to ensure it's not called + mock_event_handlers = Mock() + tracer.event_handlers = mock_event_handlers + + tracer._track_locals_change(frame, lineno) + + # Verify no interactions with event_handlers + mock_event_handlers.handle_upd.assert_not_called() + + def test_track_locals_change_added_vars(self, monkeypatch): + """Test handling of newly added local variables.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True) + tracer = Tracer(config) + frame = Mock(spec=FrameType) + lineno = 10 + + # Set up tracked_locals with existing variables + tracer.tracked_locals = {frame: {'existing_var': 'old_value'}} + tracer.tracked_locals_lens = {frame: {}} + + # Set up current frame locals with new variable + frame.f_locals = { + 'existing_var': 'old_value', + 'new_var': 'new_value', + 'self': 'self_value', + 'callable_func': lambda x: x, + } + + # Mock event_handlers + mock_event_handlers = Mock() + tracer.event_handlers = mock_event_handlers + tracer.call_depth = 1 + tracer.index_info = 'test_index' + tracer.abc_wrapper = None + + tracer._track_locals_change(frame, lineno) + + # Verify handle_upd was called for the new variable + mock_event_handlers.handle_upd.assert_called_once_with( + lineno, + class_name=Constants.HANDLE_LOCALS_SYMBOL, + key='new_var', + old_value=None, + current_value='new_value', + call_depth=1, + index_info='test_index', + abc_wrapper=None, + ) + + # Verify tracked_locals was updated + assert tracer.tracked_locals[frame] == {'existing_var': 'old_value', 'new_var': 'new_value'} + + def test_track_locals_change_added_sequence_var(self, monkeypatch): + """Test handling of newly added sequence local variable.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True) + tracer = Tracer(config) + frame = Mock(spec=FrameType) + lineno = 10 + + # Set up tracked_locals with existing variables + tracer.tracked_locals = {frame: {'existing_var': 'old_value'}} + tracer.tracked_locals_lens = {frame: {}} + + # Set up current frame locals with new sequence variable + new_list = [1, 2, 3] + frame.f_locals = {'existing_var': 'old_value', 'new_list': new_list, 'self': 'self_value'} + + # Mock event_handlers + mock_event_handlers = Mock() + tracer.event_handlers = mock_event_handlers + tracer.call_depth = 1 + tracer.index_info = 'test_index' + tracer.abc_wrapper = None + + tracer._track_locals_change(frame, lineno) + + # Verify handle_upd was called for the new sequence variable + mock_event_handlers.handle_upd.assert_called_once_with( + lineno, + class_name=Constants.HANDLE_LOCALS_SYMBOL, + key='new_list', + old_value=None, + current_value=new_list, + call_depth=1, + index_info='test_index', + abc_wrapper=None, + ) + + # Verify tracked_locals_lens was updated for sequence + assert tracer.tracked_locals_lens[frame]['new_list'] == len(new_list) + assert tracer.tracked_locals[frame] == {'existing_var': 'old_value', 'new_list': new_list} + + def test_track_locals_change_common_vars(self, monkeypatch): + """Test handling of common local variables that exist in both old and current.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True) + tracer = Tracer(config) + frame = Mock(spec=FrameType) + lineno = 10 + + # Set up tracked_locals with existing variables + old_value = 'old_value' + tracer.tracked_locals = {frame: {'common_var': old_value}} + tracer.tracked_locals_lens = {frame: {}} + + # Set up current frame locals with updated variable + current_value = 'new_value' + frame.f_locals = {'common_var': current_value, 'self': 'self_value'} + + # Mock _handle_change_type + mock_handle_change = Mock() + tracer._handle_change_type = mock_handle_change + tracer.call_depth = 1 + tracer.index_info = 'test_index' + tracer.abc_wrapper = None + + tracer._track_locals_change(frame, lineno) + + # Verify _handle_change_type was called for the common variable + mock_handle_change.assert_called_once_with( + lineno, + Constants.HANDLE_LOCALS_SYMBOL, + 'common_var', + old_value, + current_value, + None, # old_local_len + None, # current_local_len + ) + + # Verify tracked_locals was updated + assert tracer.tracked_locals[frame] == {'common_var': current_value} + + def test_track_locals_change_common_sequence_vars(self, monkeypatch): + """Test handling of common sequence local variables with length tracking.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True) + tracer = Tracer(config) + frame = Mock(spec=FrameType) + lineno = 10 + + # Set up tracked_locals with existing sequence variable + old_list = [1, 2] + tracer.tracked_locals = {frame: {'list_var': old_list}} + tracer.tracked_locals_lens = {frame: {'list_var': len(old_list)}} + + # Set up current frame locals with updated sequence variable + current_list = [1, 2, 3, 4] + frame.f_locals = {'list_var': current_list, 'self': 'self_value'} + + # Mock _handle_change_type + mock_handle_change = Mock() + tracer._handle_change_type = mock_handle_change + tracer.call_depth = 1 + tracer.index_info = 'test_index' + tracer.abc_wrapper = None + + tracer._track_locals_change(frame, lineno) + + # Verify _handle_change_type was called for the sequence variable with lengths + mock_handle_change.assert_called_once_with( + lineno, + Constants.HANDLE_LOCALS_SYMBOL, + 'list_var', + old_list, + current_list, + len(old_list), # old_local_len + len(current_list), # current_local_len + ) + + # Verify tracked_locals_lens was updated + assert tracer.tracked_locals_lens[frame]['list_var'] == len(current_list) + assert tracer.tracked_locals[frame] == {'list_var': current_list} + + def test_track_locals_change_mixed_vars(self, monkeypatch): + """Test handling of mixed added and common variables.""" + config = ObjWatchConfig(targets=['test_module'], with_locals=True) + tracer = Tracer(config) + frame = Mock(spec=FrameType) + lineno = 10 + + # Set up tracked_locals with existing variables + tracer.tracked_locals = {frame: {'common_var': 'old_value'}} + tracer.tracked_locals_lens = {frame: {}} + + # Set up current frame locals with mixed variables + frame.f_locals = { + 'common_var': 'updated_value', + 'new_var': 'new_value', + 'new_list': [1, 2, 3], + 'self': 'self_value', + 'callable_func': lambda x: x, + } + + # Mock event handlers + mock_event_handlers = Mock() + tracer.event_handlers = mock_event_handlers + mock_handle_change = Mock() + tracer._handle_change_type = mock_handle_change + tracer.call_depth = 1 + tracer.index_info = 'test_index' + tracer.abc_wrapper = None + + tracer._track_locals_change(frame, lineno) + + # Verify handle_upd was called for both new variables + assert mock_event_handlers.handle_upd.call_count == 2 + + # Verify _handle_change_type was called for common variable + mock_handle_change.assert_called_once_with( + lineno, Constants.HANDLE_LOCALS_SYMBOL, 'common_var', 'old_value', 'updated_value', None, None + ) + + # Verify tracked_locals_lens was updated for sequence + assert tracer.tracked_locals_lens[frame]['new_list'] == 3 + + # Verify final tracked_locals state + expected_locals = {'common_var': 'updated_value', 'new_var': 'new_value', 'new_list': [1, 2, 3]} + assert tracer.tracked_locals[frame] == expected_locals diff --git a/tests/test_coverup_41.py b/tests/test_coverup_41.py new file mode 100644 index 0000000..83230c7 --- /dev/null +++ b/tests/test_coverup_41.py @@ -0,0 +1,57 @@ +# file: objwatch/mp_handls.py:56-61 +# asked: {"lines": [56, 60, 61], "branches": [[60, 0], [60, 61]]} +# gained: {"lines": [56, 60, 61], "branches": [[60, 0], [60, 61]]} + +import pytest +from unittest.mock import Mock, patch +from objwatch.mp_handls import MPHandls + + +class TestMPHandlsSync: + """Test cases for MPHandls.sync method to achieve full coverage.""" + + def test_sync_when_initialized_and_sync_fn_exists(self): + """Test sync() when both initialized is True and sync_fn is not None.""" + # Create MPHandls instance + handler = MPHandls(framework=None) + + # Set up the conditions for the branch to execute + handler.initialized = True + mock_sync_fn = Mock() + handler.sync_fn = mock_sync_fn + + # Call sync method + handler.sync() + + # Verify sync_fn was called + mock_sync_fn.assert_called_once() + + def test_sync_when_not_initialized(self): + """Test sync() when initialized is False (sync_fn should not be called).""" + # Create MPHandls instance + handler = MPHandls(framework=None) + + # Set up conditions where initialized is False + handler.initialized = False + mock_sync_fn = Mock() + handler.sync_fn = mock_sync_fn + + # Call sync method + handler.sync() + + # Verify sync_fn was NOT called + mock_sync_fn.assert_not_called() + + def test_sync_when_sync_fn_is_none(self): + """Test sync() when sync_fn is None (should not call anything).""" + # Create MPHandls instance + handler = MPHandls(framework=None) + + # Set up conditions where initialized is True but sync_fn is None + handler.initialized = True + handler.sync_fn = None + + # Call sync method - should not raise any exceptions + handler.sync() + + # No assertions needed - just verifying no exceptions are raised diff --git a/tests/test_coverup_42.py b/tests/test_coverup_42.py new file mode 100644 index 0000000..18986e6 --- /dev/null +++ b/tests/test_coverup_42.py @@ -0,0 +1,155 @@ +# file: objwatch/targets.py:392-415 +# asked: {"lines": [392, 401, 402, 403, 404, 408, 409, 412, 413, 414, 415], "branches": [[408, 409], [408, 412], [412, 0], [412, 413], [413, 412], [413, 414]]} +# gained: {"lines": [392, 401, 402, 403, 404, 408, 409, 412, 413, 414, 415], "branches": [[408, 409], [408, 412], [412, 0], [412, 413], [413, 412], [413, 414]]} + +import pytest + + +class TestTargetsFlattenModuleStructure: + """Test cases for Targets._flatten_module_structure method.""" + + def test_empty_module_structure(self): + """Test with empty module structure - should not add anything to result.""" + from objwatch.targets import Targets + + targets = Targets(targets={}, exclude_targets=None) + result = {} + module_structure = {} + + targets._flatten_module_structure("test.module", module_structure, result) + + assert result == {} + + def test_module_with_only_classes(self): + """Test module structure containing only classes.""" + from objwatch.targets import Targets + + targets = Targets(targets={}, exclude_targets=None) + result = {} + module_structure = {'classes': {'TestClass': {}}, 'functions': [], 'globals': []} + + targets._flatten_module_structure("test.module", module_structure, result) + + assert "test.module" in result + assert result["test.module"]["classes"] == {'TestClass': {}} + assert result["test.module"]["functions"] == [] + assert result["test.module"]["globals"] == [] + + def test_module_with_only_functions(self): + """Test module structure containing only functions.""" + from objwatch.targets import Targets + + targets = Targets(targets={}, exclude_targets=None) + result = {} + module_structure = {'classes': {}, 'functions': ['func1', 'func2'], 'globals': []} + + targets._flatten_module_structure("test.module", module_structure, result) + + assert "test.module" in result + assert result["test.module"]["classes"] == {} + assert result["test.module"]["functions"] == ['func1', 'func2'] + assert result["test.module"]["globals"] == [] + + def test_module_with_only_globals(self): + """Test module structure containing only globals.""" + from objwatch.targets import Targets + + targets = Targets(targets={}, exclude_targets=None) + result = {} + module_structure = {'classes': {}, 'functions': [], 'globals': ['CONSTANT', 'VARIABLE']} + + targets._flatten_module_structure("test.module", module_structure, result) + + assert "test.module" in result + assert result["test.module"]["classes"] == {} + assert result["test.module"]["functions"] == [] + assert result["test.module"]["globals"] == ['CONSTANT', 'VARIABLE'] + + def test_module_with_nested_submodules(self): + """Test module structure with nested submodules.""" + from objwatch.targets import Targets + + targets = Targets(targets={}, exclude_targets=None) + result = {} + module_structure = { + 'classes': {'MainClass': {}}, + 'functions': ['main_func'], + 'submodule1': { + 'classes': {'SubClass1': {}}, + 'functions': ['sub_func1'], + 'subsubmodule': {'classes': {'DeepClass': {}}, 'functions': ['deep_func']}, + }, + 'submodule2': {'classes': {'SubClass2': {}}, 'functions': ['sub_func2']}, + } + + targets._flatten_module_structure("test", module_structure, result) + + # Check main module + assert "test" in result + assert result["test"]["classes"] == {'MainClass': {}} + assert result["test"]["functions"] == ['main_func'] + + # Check submodule1 + assert "test.submodule1" in result + assert result["test.submodule1"]["classes"] == {'SubClass1': {}} + assert result["test.submodule1"]["functions"] == ['sub_func1'] + + # Check subsubmodule + assert "test.submodule1.subsubmodule" in result + assert result["test.submodule1.subsubmodule"]["classes"] == {'DeepClass': {}} + assert result["test.submodule1.subsubmodule"]["functions"] == ['deep_func'] + + # Check submodule2 + assert "test.submodule2" in result + assert result["test.submodule2"]["classes"] == {'SubClass2': {}} + assert result["test.submodule2"]["functions"] == ['sub_func2'] + + def test_module_with_mixed_content_and_extra_keys(self): + """Test module structure with mixed content and extra non-dict keys.""" + from objwatch.targets import Targets + + targets = Targets(targets={}, exclude_targets=None) + result = {} + module_structure = { + 'classes': {'TestClass': {}}, + 'functions': ['test_func'], + 'globals': ['TEST_VAR'], + 'extra_string': 'should_be_ignored', + 'extra_number': 42, + 'nested_module': {'classes': {'NestedClass': {}}, 'functions': ['nested_func']}, + } + + targets._flatten_module_structure("test", module_structure, result) + + # Check main module + assert "test" in result + assert result["test"]["classes"] == {'TestClass': {}} + assert result["test"]["functions"] == ['test_func'] + assert result["test"]["globals"] == ['TEST_VAR'] + + # Check nested module + assert "test.nested_module" in result + assert result["test.nested_module"]["classes"] == {'NestedClass': {}} + assert result["test.nested_module"]["functions"] == ['nested_func'] + + # Verify extra non-dict keys were ignored + assert 'test.extra_string' not in result + assert 'test.extra_number' not in result + + def test_module_with_no_standard_sections(self): + """Test module structure with no standard sections but has nested modules.""" + from objwatch.targets import Targets + + targets = Targets(targets={}, exclude_targets=None) + result = {} + module_structure = {'nested': {'classes': {'NestedClass': {}}, 'functions': ['nested_func']}} + + targets._flatten_module_structure("test", module_structure, result) + + # Main module should not be added (no standard sections) + assert "test" not in result + + # But nested module should be added + assert "test.nested" in result + assert result["test.nested"]["classes"] == {'NestedClass': {}} + assert result["test.nested"]["functions"] == ['nested_func'] diff --git a/tests/test_coverup_43.py b/tests/test_coverup_43.py new file mode 100644 index 0000000..1f7ddc8 --- /dev/null +++ b/tests/test_coverup_43.py @@ -0,0 +1,140 @@ +# file: objwatch/utils/weak.py:156-161 +# asked: {"lines": [156, 157, 158, 159, 160, 161], "branches": [[158, 0], [158, 159], [160, 158], [160, 161]]} +# gained: {"lines": [156, 157, 158, 159, 160, 161], "branches": [[158, 0], [158, 159], [160, 161]]} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, _IterationGuard + + +class TestWeakIdKeyDictionaryItems: + def test_items_yields_live_objects(self): + """Test that items() yields only live objects and skips dead references.""" + + # Create a dictionary with objects that can be weakly referenced + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + obj3 = TestObject("obj3") + + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + weak_dict[obj3] = "value3" + + # Delete one object to create a dead reference + del obj2 + + # Collect garbage to ensure the weak reference is cleared + import gc + + gc.collect() + + # Get items and verify only live objects are yielded + items = list(weak_dict.items()) + + # Should contain only obj1 and obj3 + expected_items = [(obj1, "value1"), (obj3, "value3")] + assert len(items) == 2 + assert set(items) == set(expected_items) + + # Verify the dead reference (obj2) is not in the result + for key, value in items: + assert key is not None + assert key in [obj1, obj3] + + def test_items_with_iteration_guard(self): + """Test that items() properly uses _IterationGuard context manager.""" + weak_dict = WeakIdKeyDictionary() + + # Add some items with objects that can be weakly referenced + class TestObject: + pass + + obj1 = TestObject() + obj2 = TestObject() + weak_dict[obj1] = "test1" + weak_dict[obj2] = "test2" + + # Call items() and verify it works within iteration guard + items = list(weak_dict.items()) + + assert len(items) == 2 + assert (obj1, "test1") in items + assert (obj2, "test2") in items + + # Verify iteration guard was properly cleaned up + assert len(weak_dict._iterating) == 0 + + def test_items_empty_dict(self): + """Test items() on an empty dictionary.""" + weak_dict = WeakIdKeyDictionary() + items = list(weak_dict.items()) + assert items == [] + + def test_items_with_mixed_live_dead_references(self): + """Test items() with a mix of live and dead references in the data dict.""" + weak_dict = WeakIdKeyDictionary() + + # Create objects that can be weakly referenced + class TestObject: + pass + + live_obj1 = TestObject() + live_obj2 = TestObject() + dead_obj = TestObject() + + weak_dict[live_obj1] = "live1" + weak_dict[live_obj2] = "live2" + weak_dict[dead_obj] = "dead" + + # Create a dead reference by deleting the object and forcing garbage collection + del dead_obj + import gc + + gc.collect() + + # Get items - should only yield live objects + items = list(weak_dict.items()) + + assert len(items) == 2 + assert (live_obj1, "live1") in items + assert (live_obj2, "live2") in items + + # Verify no dead references are returned + for key, value in items: + assert key is not None + assert key in [live_obj1, live_obj2] + + def test_items_generator_behavior(self): + """Test that items() returns a generator that can be consumed incrementally.""" + weak_dict = WeakIdKeyDictionary() + + class TestObject: + pass + + obj1 = TestObject() + obj2 = TestObject() + obj3 = TestObject() + + weak_dict[obj1] = 1 + weak_dict[obj2] = 2 + weak_dict[obj3] = 3 + + # Get the generator + items_gen = weak_dict.items() + + # Consume first item + first = next(items_gen) + assert first in [(obj1, 1), (obj2, 2), (obj3, 3)] + + # Consume remaining items + remaining = list(items_gen) + assert len(remaining) == 2 + + # All items should be unique + all_items = [first] + remaining + assert len(set(all_items)) == 3 diff --git a/tests/test_coverup_44.py b/tests/test_coverup_44.py new file mode 100644 index 0000000..7048c36 --- /dev/null +++ b/tests/test_coverup_44.py @@ -0,0 +1,175 @@ +# file: objwatch/event_handls.py:50-76 +# asked: {"lines": [50, 51, 52, 56, 58, 59, 60, 61, 62, 65, 66, 67, 68, 70, 71, 73, 74, 75, 76], "branches": [[65, 66], [65, 70], [73, 0], [73, 74]]} +# gained: {"lines": [50, 51, 52, 56, 58, 59, 60, 61, 62, 65, 66, 67, 68, 70, 71, 73, 74, 75, 76], "branches": [[65, 66], [65, 70], [73, 0], [73, 74]]} + +import pytest +import xml.etree.ElementTree as ET +from unittest.mock import Mock, patch, MagicMock +from objwatch.event_handls import EventHandls +from objwatch.events import EventType + + +class TestEventHandlsHandleRun: + """Test cases for EventHandls.handle_run method to achieve full coverage.""" + + def test_handle_run_without_abc_wrapper_and_output_xml_false(self, monkeypatch): + """Test handle_run without abc_wrapper and output_xml=False.""" + event_handls = EventHandls() + event_handls.output_xml = False + event_handls.current_node = [Mock()] + + func_info = { + 'qualified_name': 'test_function', + 'module': 'test_module', + 'symbol': 'test_symbol', + 'symbol_type': 'function', + } + + log_calls = [] + + def mock_log_debug(msg): + log_calls.append(msg) + + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + event_handls.handle_run(lineno=42, func_info=func_info, abc_wrapper=None, call_depth=1, index_info="[0]") + + expected_log = "[0] 42 | run test_function" + assert len(log_calls) == 1 + assert expected_log in log_calls[0] + + def test_handle_run_with_abc_wrapper_and_output_xml_false(self, monkeypatch): + """Test handle_run with abc_wrapper and output_xml=False.""" + event_handls = EventHandls() + event_handls.output_xml = False + event_handls.current_node = [Mock()] + + func_info = { + 'qualified_name': 'test_function', + 'module': 'test_module', + 'symbol': 'test_symbol', + 'symbol_type': 'function', + 'frame': Mock(), + } + + abc_wrapper = Mock() + abc_wrapper.wrap_call.return_value = "wrapped_call" + + log_calls = [] + + def mock_log_debug(msg): + log_calls.append(msg) + + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + event_handls.handle_run(lineno=42, func_info=func_info, abc_wrapper=abc_wrapper, call_depth=2, index_info="[1]") + + abc_wrapper.wrap_call.assert_called_once_with('test_symbol', func_info['frame']) + + assert len(log_calls) == 1 + assert "[1] 42 | | run test_function <- wrapped_call" in log_calls[0] + + def test_handle_run_without_abc_wrapper_and_output_xml_true(self, monkeypatch): + """Test handle_run without abc_wrapper and output_xml=True.""" + event_handls = EventHandls(output_xml="test.xml") + parent_node = Mock() + event_handls.current_node = [parent_node] + + func_info = { + 'qualified_name': 'test_function', + 'module': 'test_module', + 'symbol': 'test_symbol', + 'symbol_type': 'function', + } + + log_calls = [] + + def mock_log_debug(msg): + log_calls.append(msg) + + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + event_handls.handle_run(lineno=42, func_info=func_info, abc_wrapper=None, call_depth=0, index_info="[2]") + + assert len(log_calls) == 1 + assert "[2] 42 run test_function" in log_calls[0] + + # Verify XML element creation + assert parent_node.append.called + function_element = parent_node.append.call_args[0][0] + assert function_element.tag == 'Function' + assert function_element.attrib['module'] == 'test_module' + assert function_element.attrib['symbol'] == 'test_symbol' + assert function_element.attrib['symbol_type'] == 'function' + assert function_element.attrib['run_line'] == '42' + assert 'call_msg' not in function_element.attrib + + def test_handle_run_with_abc_wrapper_and_output_xml_true(self, monkeypatch): + """Test handle_run with abc_wrapper and output_xml=True.""" + event_handls = EventHandls(output_xml="test.xml") + parent_node = Mock() + event_handls.current_node = [parent_node] + + func_info = { + 'qualified_name': 'test_function', + 'module': 'test_module', + 'symbol': 'test_symbol', + 'symbol_type': 'method', + 'frame': Mock(), + } + + abc_wrapper = Mock() + abc_wrapper.wrap_call.return_value = "wrapped_method_call" + + log_calls = [] + + def mock_log_debug(msg): + log_calls.append(msg) + + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + event_handls.handle_run( + lineno=100, func_info=func_info, abc_wrapper=abc_wrapper, call_depth=3, index_info="[3]" + ) + + abc_wrapper.wrap_call.assert_called_once_with('test_symbol', func_info['frame']) + + assert len(log_calls) == 1 + assert "[3] 100 | | | run test_function <- wrapped_method_call" in log_calls[0] + + # Verify XML element creation with call_msg + assert parent_node.append.called + function_element = parent_node.append.call_args[0][0] + assert function_element.tag == 'Function' + assert function_element.attrib['module'] == 'test_module' + assert function_element.attrib['symbol'] == 'test_symbol' + assert function_element.attrib['symbol_type'] == 'method' + assert function_element.attrib['run_line'] == '100' + assert function_element.attrib['call_msg'] == 'wrapped_method_call' + + def test_handle_run_with_none_symbol_type(self, monkeypatch): + """Test handle_run with None symbol_type (should default to 'function').""" + event_handls = EventHandls(output_xml="test.xml") + parent_node = Mock() + event_handls.current_node = [parent_node] + + func_info = { + 'qualified_name': 'test_function', + 'module': 'test_module', + 'symbol': 'test_symbol', + 'symbol_type': None, + } + + log_calls = [] + + def mock_log_debug(msg): + log_calls.append(msg) + + monkeypatch.setattr('objwatch.event_handls.log_debug', mock_log_debug) + + event_handls.handle_run(lineno=42, func_info=func_info, abc_wrapper=None, call_depth=0, index_info="[4]") + + # Verify XML element creation with default symbol_type + assert parent_node.append.called + function_element = parent_node.append.call_args[0][0] + assert function_element.attrib['symbol_type'] == 'function' diff --git a/tests/test_coverup_45.py b/tests/test_coverup_45.py new file mode 100644 index 0000000..f76fa6f --- /dev/null +++ b/tests/test_coverup_45.py @@ -0,0 +1,135 @@ +# file: objwatch/event_handls.py:348-360 +# asked: {"lines": [348, 357, 358, 359, 360], "branches": [[357, 0], [357, 358]]} +# gained: {"lines": [348, 357, 358, 359, 360], "branches": [[357, 0], [357, 358]]} + +import pytest +import signal +import tempfile +import os +import builtins +from unittest.mock import Mock, patch, call +from objwatch.event_handls import EventHandls + + +class TestEventHandlsSignalHandler: + """Test cases for EventHandls.signal_handler method.""" + + def test_signal_handler_when_xml_not_saved(self, monkeypatch): + """Test signal_handler when XML has not been saved yet.""" + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as temp_file: + temp_path = temp_file.name + + try: + # Create EventHandls instance with XML output + event_handls = EventHandls(output_xml=temp_path) + + # Mock dependencies + mock_log_error = Mock() + mock_save_xml = Mock() + mock_exit = Mock() + + monkeypatch.setattr('objwatch.event_handls.log_error', mock_log_error) + monkeypatch.setattr(event_handls, 'save_xml', mock_save_xml) + monkeypatch.setattr('builtins.exit', mock_exit) + + # Set initial state + event_handls.is_xml_saved = False + + # Call signal_handler + test_signal = signal.SIGTERM + test_frame = Mock() + event_handls.signal_handler(test_signal, test_frame) + + # Verify the calls + mock_log_error.assert_called_once_with(f"Received signal {test_signal}, saving XML before exiting.") + mock_save_xml.assert_called_once() + mock_exit.assert_called_once_with(1) + + finally: + # Cleanup + if os.path.exists(temp_path): + os.unlink(temp_path) + + def test_signal_handler_when_xml_already_saved(self, monkeypatch): + """Test signal_handler when XML has already been saved.""" + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as temp_file: + temp_path = temp_file.name + + try: + # Create EventHandls instance with XML output + event_handls = EventHandls(output_xml=temp_path) + + # Mock dependencies + mock_log_error = Mock() + mock_save_xml = Mock() + mock_exit = Mock() + + monkeypatch.setattr('objwatch.event_handls.log_error', mock_log_error) + monkeypatch.setattr(event_handls, 'save_xml', mock_save_xml) + monkeypatch.setattr('builtins.exit', mock_exit) + + # Set initial state - XML already saved + event_handls.is_xml_saved = True + + # Call signal_handler + test_signal = signal.SIGINT + test_frame = Mock() + event_handls.signal_handler(test_signal, test_frame) + + # Verify no calls were made + mock_log_error.assert_not_called() + mock_save_xml.assert_not_called() + mock_exit.assert_not_called() + + finally: + # Cleanup + if os.path.exists(temp_path): + os.unlink(temp_path) + + def test_signal_handler_with_different_signals(self, monkeypatch): + """Test signal_handler with various signal types.""" + test_signals = [ + signal.SIGTERM, + signal.SIGINT, + signal.SIGABRT, + signal.SIGHUP, + signal.SIGQUIT, + signal.SIGUSR1, + signal.SIGUSR2, + signal.SIGALRM, + signal.SIGSEGV, + ] + + for test_signal in test_signals: + with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as temp_file: + temp_path = temp_file.name + + try: + # Create EventHandls instance with XML output + event_handls = EventHandls(output_xml=temp_path) + + # Mock dependencies + mock_log_error = Mock() + mock_save_xml = Mock() + mock_exit = Mock() + + monkeypatch.setattr('objwatch.event_handls.log_error', mock_log_error) + monkeypatch.setattr(event_handls, 'save_xml', mock_save_xml) + monkeypatch.setattr('builtins.exit', mock_exit) + + # Set initial state + event_handls.is_xml_saved = False + + # Call signal_handler + test_frame = Mock() + event_handls.signal_handler(test_signal, test_frame) + + # Verify the calls + mock_log_error.assert_called_once_with(f"Received signal {test_signal}, saving XML before exiting.") + mock_save_xml.assert_called_once() + mock_exit.assert_called_once_with(1) + + finally: + # Cleanup + if os.path.exists(temp_path): + os.unlink(temp_path) diff --git a/tests/test_coverup_46.py b/tests/test_coverup_46.py new file mode 100644 index 0000000..602994c --- /dev/null +++ b/tests/test_coverup_46.py @@ -0,0 +1,86 @@ +# file: objwatch/utils/weak.py:219-224 +# asked: {"lines": [219, 220, 221, 222, 223, 224], "branches": [[220, 221], [220, 224]]} +# gained: {"lines": [219, 220, 221, 222, 223, 224], "branches": [[220, 221], [220, 224]]} + +import pytest +from collections.abc import MutableMapping +import collections.abc as _collections_abc + + +class MockMapping(MutableMapping): + def __init__(self, data=None): + self._data = data or {} + + def __getitem__(self, key): + return self._data[key] + + def __setitem__(self, key, value): + self._data[key] = value + + def __delitem__(self, key): + del self._data[key] + + def __iter__(self): + return iter(self._data) + + def __len__(self): + return len(self._data) + + +class TestWeakIdKeyDictionaryOr: + def test_or_with_mapping(self, monkeypatch): + """Test __or__ method with a valid Mapping object""" + from objwatch.utils.weak import WeakIdKeyDictionary + + # Mock the ref_type to avoid weak reference issues + class MockRef: + def __init__(self, key, callback=None): + self.key = key + self.callback = callback + + def __hash__(self): + return id(self.key) + + def __eq__(self, other): + return isinstance(other, MockRef) and self.key == other.key + + def __call__(self): + return self.key + + # Create a WeakIdKeyDictionary with mocked ref_type + dict1 = WeakIdKeyDictionary() + monkeypatch.setattr(dict1, 'ref_type', MockRef) + + # Create test objects that can be weakly referenced + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + obj3 = TestObject("obj3") + + dict1[obj1] = "value1" + dict1[obj2] = "value2" + + dict2 = MockMapping({obj3: "value3"}) + + result = dict1 | dict2 + + assert obj1 in result + assert obj2 in result + assert obj3 in result + assert result[obj1] == "value1" + assert result[obj2] == "value2" + assert result[obj3] == "value3" + assert len(result) == 3 + + def test_or_with_non_mapping(self): + """Test __or__ method with a non-Mapping object returns NotImplemented""" + from objwatch.utils.weak import WeakIdKeyDictionary + + dict1 = WeakIdKeyDictionary() + non_mapping = "not_a_mapping" + + result = dict1.__or__(non_mapping) + assert result is NotImplemented diff --git a/tests/test_coverup_47.py b/tests/test_coverup_47.py new file mode 100644 index 0000000..aed564c --- /dev/null +++ b/tests/test_coverup_47.py @@ -0,0 +1,286 @@ +# file: objwatch/tracer.py:691-771 +# asked: {"lines": [691, 699, 714, 715, 717, 719, 720, 721, 722, 724, 726, 727, 729, 730, 731, 732, 735, 736, 737, 738, 739, 740, 741, 743, 745, 747, 748, 749, 750, 751, 755, 756, 757, 759, 761, 763, 764, 765, 767, 769, 771], "branches": [[714, 715], [714, 717], [717, 719], [717, 722], [719, 720], [719, 726], [722, 724], [722, 726], [727, 729], [727, 745], [735, 736], [735, 743], [739, 740], [739, 743], [740, 739], [740, 741], [745, 747], [745, 761], [755, 756], [755, 759], [761, 763], [761, 769]]} +# gained: {"lines": [691, 699, 714, 715, 717, 719, 720, 721, 722, 724, 726, 727, 729, 730, 731, 732, 735, 736, 737, 738, 739, 740, 741, 743, 745, 747, 748, 749, 750, 751, 755, 756, 757, 759, 761, 763, 764, 765, 767, 769, 771], "branches": [[714, 715], [714, 717], [717, 719], [717, 722], [719, 720], [719, 726], [722, 724], [727, 729], [727, 745], [735, 736], [735, 743], [739, 740], [739, 743], [740, 739], [740, 741], [745, 747], [745, 761], [755, 756], [761, 763], [761, 769]]} + +import pytest +import sys +from types import FrameType +from unittest.mock import Mock, MagicMock, patch +import objwatch.tracer +from objwatch.constants import Constants + + +class TestTracerTraceFactory: + """Test cases for Tracer.trace_factory method to achieve full coverage.""" + + def test_trace_factory_should_not_trace_frame(self, monkeypatch): + """Test that trace_func returns early when _should_trace_frame returns False.""" + from objwatch.tracer import Tracer + from objwatch.config import ObjWatchConfig + + config = Mock(spec=ObjWatchConfig) + config.with_locals = False + config.with_globals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.indexes = [0] + config.wrapper = None + + tracer = Tracer(config) + tracer._should_trace_frame = Mock(return_value=False) + tracer.current_index = None + + trace_func = tracer.trace_factory() + + mock_frame = Mock(spec=FrameType) + mock_frame.f_lineno = 10 + + # Should return trace_func immediately without further processing + result = trace_func(mock_frame, "call", None) + assert result is trace_func + + tracer._should_trace_frame.assert_called_once_with(mock_frame) + + def test_trace_factory_current_index_not_in_indexes(self, monkeypatch): + """Test that trace_func returns early when current_index is not in indexes.""" + from objwatch.tracer import Tracer + from objwatch.config import ObjWatchConfig + + config = Mock(spec=ObjWatchConfig) + config.with_locals = False + config.with_globals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.indexes = [2, 3] + config.wrapper = None + + tracer = Tracer(config) + tracer._should_trace_frame = Mock(return_value=True) + tracer.current_index = 1 + tracer.indexes = {2, 3} # current_index 1 not in indexes + + trace_func = tracer.trace_factory() + + mock_frame = Mock(spec=FrameType) + mock_frame.f_lineno = 10 + + # Should return trace_func immediately without further processing + result = trace_func(mock_frame, "call", None) + assert result is trace_func + + tracer._should_trace_frame.assert_called_once_with(mock_frame) + + def test_trace_factory_call_event_with_locals(self, monkeypatch): + """Test call event handling with locals tracking enabled.""" + from objwatch.tracer import Tracer + from objwatch.config import ObjWatchConfig + + config = Mock(spec=ObjWatchConfig) + config.with_locals = True + config.with_globals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.indexes = [0, 1] + config.wrapper = None + + tracer = Tracer(config) + tracer._should_trace_frame = Mock(return_value=True) + tracer.current_index = None + tracer.indexes = {0, 1} + tracer.mp_handlers = Mock() + tracer.mp_handlers.is_initialized.return_value = False + tracer._get_function_info = Mock(return_value={"func": "info"}) + tracer._update_objects_lens = Mock() + tracer.event_handlers = Mock() + tracer.abc_wrapper = Mock() + tracer.call_depth = 0 + tracer.index_info = "" + + trace_func = tracer.trace_factory() + + mock_frame = Mock(spec=FrameType) + mock_frame.f_lineno = 10 + mock_frame.f_locals = {"x": [1, 2, 3], "y": "hello", "self": Mock(), "func": lambda x: x} + mock_frame.f_code.co_filename = "test.py" + + result = trace_func(mock_frame, "call", None) + + assert result is trace_func + tracer._get_function_info.assert_called_once_with(mock_frame) + tracer._update_objects_lens.assert_called_once_with(mock_frame) + tracer.event_handlers.handle_run.assert_called_once_with(10, {"func": "info"}, tracer.abc_wrapper, 0, "") + assert tracer.call_depth == 1 + assert mock_frame in tracer.tracked_locals + assert "x" in tracer.tracked_locals[mock_frame] + assert "y" in tracer.tracked_locals[mock_frame] + assert "self" not in tracer.tracked_locals[mock_frame] + assert "func" not in tracer.tracked_locals[mock_frame] + assert mock_frame in tracer.tracked_locals_lens + assert tracer.tracked_locals_lens[mock_frame]["x"] == 3 + + def test_trace_factory_return_event_with_locals(self, monkeypatch): + """Test return event handling with locals tracking enabled.""" + from objwatch.tracer import Tracer + from objwatch.config import ObjWatchConfig + + config = Mock(spec=ObjWatchConfig) + config.with_locals = True + config.with_globals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.indexes = [0, 1] + config.wrapper = None + + tracer = Tracer(config) + tracer._should_trace_frame = Mock(return_value=True) + tracer.current_index = None + tracer.indexes = {0, 1} + tracer.mp_handlers = Mock() + tracer.mp_handlers.is_initialized.return_value = False + tracer._get_function_info = Mock(return_value={"func": "info"}) + tracer._update_objects_lens = Mock() + tracer.event_handlers = Mock() + tracer.abc_wrapper = Mock() + tracer.call_depth = 2 + tracer.index_info = "" + + # Add frame to tracked_locals to test cleanup + mock_frame = Mock(spec=FrameType) + tracer.tracked_locals[mock_frame] = {"x": 1} + tracer.tracked_locals_lens[mock_frame] = {"x": 1} + + trace_func = tracer.trace_factory() + + mock_frame.f_lineno = 20 + + result = trace_func(mock_frame, "return", "return_value") + + assert result is trace_func + assert tracer.call_depth == 1 + tracer._get_function_info.assert_called_once_with(mock_frame) + tracer._update_objects_lens.assert_called_once_with(mock_frame) + tracer.event_handlers.handle_end.assert_called_once_with( + 20, {"func": "info"}, tracer.abc_wrapper, 1, "", "return_value" + ) + assert mock_frame not in tracer.tracked_locals + assert mock_frame not in tracer.tracked_locals_lens + + def test_trace_factory_line_event(self, monkeypatch): + """Test line event handling.""" + from objwatch.tracer import Tracer + from objwatch.config import ObjWatchConfig + + config = Mock(spec=ObjWatchConfig) + config.with_locals = False + config.with_globals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.indexes = [0, 1] + config.wrapper = None + + tracer = Tracer(config) + tracer._should_trace_frame = Mock(return_value=True) + tracer.current_index = None + tracer.indexes = {0, 1} + tracer.mp_handlers = Mock() + tracer.mp_handlers.is_initialized.return_value = False + tracer._track_object_change = Mock() + tracer._track_locals_change = Mock() + tracer._track_globals_change = Mock() + + trace_func = tracer.trace_factory() + + mock_frame = Mock(spec=FrameType) + mock_frame.f_lineno = 30 + + result = trace_func(mock_frame, "line", None) + + assert result is trace_func + tracer._track_object_change.assert_called_once_with(mock_frame, 30) + tracer._track_locals_change.assert_called_once_with(mock_frame, 30) + tracer._track_globals_change.assert_called_once_with(mock_frame, 30) + + def test_trace_factory_mp_initialized(self, monkeypatch): + """Test multi-process framework initialization.""" + from objwatch.tracer import Tracer + from objwatch.config import ObjWatchConfig + + config = Mock(spec=ObjWatchConfig) + config.with_locals = False + config.with_globals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.indexes = [0, 1] + config.wrapper = None + + tracer = Tracer(config) + tracer._should_trace_frame = Mock(return_value=True) + tracer.current_index = None + tracer.indexes = {0, 1} + tracer.mp_handlers = Mock() + tracer.mp_handlers.is_initialized.return_value = True + tracer.mp_handlers.get_index.return_value = 0 + tracer._get_function_info = Mock(return_value={"func": "info"}) + tracer._update_objects_lens = Mock() + tracer.event_handlers = Mock() + tracer.abc_wrapper = Mock() + tracer.call_depth = 0 + tracer.index_info = "" + + trace_func = tracer.trace_factory() + + mock_frame = Mock(spec=FrameType) + mock_frame.f_lineno = 10 + mock_frame.f_locals = {} + + result = trace_func(mock_frame, "call", None) + + assert result is trace_func + assert tracer.current_index == 0 + assert tracer.index_info == "[#0] " + tracer.mp_handlers.is_initialized.assert_called_once() + tracer.mp_handlers.get_index.assert_called_once() + + def test_trace_factory_unknown_event(self, monkeypatch): + """Test handling of unknown event types.""" + from objwatch.tracer import Tracer + from objwatch.config import ObjWatchConfig + + config = Mock(spec=ObjWatchConfig) + config.with_locals = False + config.with_globals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.indexes = [0, 1] + config.wrapper = None + + tracer = Tracer(config) + tracer._should_trace_frame = Mock(return_value=True) + tracer.current_index = None + tracer.indexes = {0, 1} + tracer.mp_handlers = Mock() + tracer.mp_handlers.is_initialized.return_value = False + + trace_func = tracer.trace_factory() + + mock_frame = Mock(spec=FrameType) + mock_frame.f_lineno = 40 + + # Test with unknown event type + result = trace_func(mock_frame, "unknown_event", None) + + assert result is trace_func diff --git a/tests/test_coverup_48.py b/tests/test_coverup_48.py new file mode 100644 index 0000000..d2a0c97 --- /dev/null +++ b/tests/test_coverup_48.py @@ -0,0 +1,82 @@ +# file: objwatch/targets.py:214-228 +# asked: {"lines": [214, 223, 224, 225, 226, 227, 228], "branches": []} +# gained: {"lines": [214, 223, 224, 225, 226, 227, 228], "branches": []} + +import pytest +import inspect +from unittest.mock import Mock, patch +import sys + + +class TestTargetsParseClass: + """Test cases for Targets._parse_class method""" + + def test_parse_class_with_module(self): + """Test _parse_class with a class that has a module""" + from objwatch.targets import Targets + + # Create a mock class with a module + mock_module = Mock() + mock_module.__name__ = 'test_module' + + class MockClass: + pass + + # Mock inspect.getmodule to return our mock module + with patch('objwatch.targets.inspect.getmodule', return_value=mock_module): + targets = Targets(targets=[]) + module_name, parsed_structure = targets._parse_class(MockClass) + + assert module_name == 'test_module' + assert parsed_structure == { + 'classes': {'MockClass': {'methods': [], 'attributes': [], 'track_all': True}}, + 'functions': [], + 'globals': [], + } + + def test_parse_class_without_module(self): + """Test _parse_class with a class that has no module""" + from objwatch.targets import Targets + + class MockClass: + pass + + # Mock inspect.getmodule to return None (no module) + with patch('objwatch.targets.inspect.getmodule', return_value=None): + targets = Targets(targets=[]) + module_name, parsed_structure = targets._parse_class(MockClass) + + assert module_name == '' + assert parsed_structure == { + 'classes': {'MockClass': {'methods': [], 'attributes': [], 'track_all': True}}, + 'functions': [], + 'globals': [], + } + + def test_parse_class_with_builtin_type(self): + """Test _parse_class with a built-in type""" + from objwatch.targets import Targets + + # Test with a built-in type like int + targets = Targets(targets=[]) + module_name, parsed_structure = targets._parse_class(int) + + # int should have a module (builtins) + assert module_name == 'builtins' + assert parsed_structure == { + 'classes': {'int': {'methods': [], 'attributes': [], 'track_all': True}}, + 'functions': [], + 'globals': [], + } + + def test_parse_class_with_custom_class_name(self): + """Test _parse_class with a class that has a custom name""" + from objwatch.targets import Targets + + class CustomClassName: + pass + + targets = Targets(targets=[]) + module_name, parsed_structure = targets._parse_class(CustomClassName) + + assert parsed_structure['classes']['CustomClassName'] == {'methods': [], 'attributes': [], 'track_all': True} diff --git a/tests/test_coverup_49.py b/tests/test_coverup_49.py new file mode 100644 index 0000000..28c5440 --- /dev/null +++ b/tests/test_coverup_49.py @@ -0,0 +1,31 @@ +# file: objwatch/core.py:70-75 +# asked: {"lines": [70, 74, 75], "branches": []} +# gained: {"lines": [70, 74, 75], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.core import ObjWatch +from objwatch.utils.logger import log_info + + +class TestObjWatchStart: + def test_start_calls_log_info_and_tracer_start(self, monkeypatch): + # Mock the log_info function + mock_log_info = Mock() + monkeypatch.setattr("objwatch.core.log_info", mock_log_info) + + # Mock the tracer's start method + mock_tracer_start = Mock() + + # Create ObjWatch instance with minimal configuration + objwatch = ObjWatch(targets=["some_module"]) + objwatch.tracer.start = mock_tracer_start + + # Call the start method + objwatch.start() + + # Verify log_info was called with the expected message + mock_log_info.assert_called_once_with("Starting ObjWatch tracing.") + + # Verify tracer.start was called + mock_tracer_start.assert_called_once() diff --git a/tests/test_coverup_5.py b/tests/test_coverup_5.py new file mode 100644 index 0000000..710fc25 --- /dev/null +++ b/tests/test_coverup_5.py @@ -0,0 +1,119 @@ +# file: objwatch/event_handls.py:307-326 +# asked: {"lines": [307, 308, 318, 319, 320, 321, 323, 324, 325, 326], "branches": [[318, 319], [318, 320], [320, 321], [320, 323]]} +# gained: {"lines": [307, 308, 318, 319, 320, 321, 323, 324, 325, 326], "branches": [[318, 319], [318, 320], [320, 321], [320, 323]]} + +import pytest +from objwatch.event_handls import EventHandls +from objwatch.constants import Constants +from enum import Enum +from types import FunctionType + + +class TestEnum(Enum): + VALUE1 = 1 + VALUE2 = 2 + + +def dummy_function(): + pass + + +class CustomClass: + def __init__(self, name): + self.name = name + + +class TestEventHandlsFormatValue: + + def test_format_value_log_element_types(self): + """Test _format_value with LOG_ELEMENT_TYPES""" + # Test bool + assert EventHandls._format_value(True) == "True" + assert EventHandls._format_value(False) == "False" + + # Test int + assert EventHandls._format_value(42) == "42" + + # Test float + assert EventHandls._format_value(3.14) == "3.14" + + # Test str + assert EventHandls._format_value("hello") == "hello" + + # Test None + assert EventHandls._format_value(None) == "None" + + # Test FunctionType + assert EventHandls._format_value(dummy_function) == f"{dummy_function}" + + # Test Enum + assert EventHandls._format_value(TestEnum.VALUE1) == f"{TestEnum.VALUE1}" + + def test_format_value_log_sequence_types(self): + """Test _format_value with LOG_SEQUENCE_TYPES""" + # Test list + test_list = [1, 2, 3] + result = EventHandls._format_value(test_list) + assert result == EventHandls.format_sequence(test_list) + + # Test set + test_set = {1, 2, 3} + result = EventHandls._format_value(test_set) + assert result == EventHandls.format_sequence(test_set) + + # Test dict + test_dict = {"a": 1, "b": 2} + result = EventHandls._format_value(test_dict) + assert result == EventHandls.format_sequence(test_dict) + + # Test tuple + test_tuple = (1, 2, 3) + result = EventHandls._format_value(test_tuple) + assert result == EventHandls.format_sequence(test_tuple) + + def test_format_value_with_name_attribute(self): + """Test _format_value with objects that have __name__ attribute""" + # Test class with __name__ + result = EventHandls._format_value(CustomClass) + assert result == "(type)CustomClass" + + # Test function with __name__ + def test_func(): + pass + + result = EventHandls._format_value(test_func) + assert result == f"{test_func}" # FunctionType is LOG_ELEMENT_TYPE + + def test_format_value_without_name_attribute(self): + """Test _format_value with objects that don't have __name__ attribute""" + # Test class instance without __name__ + obj = CustomClass("test") + result = EventHandls._format_value(obj) + assert result == "(type)CustomClass" + + # Test list instance without __name__ + obj = [1, 2, 3] + result = EventHandls._format_value(obj) + assert result == EventHandls.format_sequence(obj) # list is LOG_SEQUENCE_TYPE + + def test_format_value_edge_cases(self): + """Test _format_value with edge cases""" + + # Test object that raises exception when accessing __name__ + class NoNameClass: + @property + def __name__(self): + raise AttributeError("No __name__") + + obj = NoNameClass() + result = EventHandls._format_value(obj) + assert result == "(type)NoNameClass" + + # Test with complex object that doesn't fit other categories + class ComplexObject: + def __repr__(self): + return "ComplexObject()" + + obj = ComplexObject() + result = EventHandls._format_value(obj) + assert result == "(type)ComplexObject" diff --git a/tests/test_coverup_50.py b/tests/test_coverup_50.py new file mode 100644 index 0000000..59e202d --- /dev/null +++ b/tests/test_coverup_50.py @@ -0,0 +1,89 @@ +# file: objwatch/utils/weak.py:111-116 +# asked: {"lines": [111, 112, 115, 116], "branches": [[112, 115], [112, 116]]} +# gained: {"lines": [111, 112, 115, 116], "branches": [[112, 115], [112, 116]]} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryLen: + def test_len_with_dirty_len_and_pending_removals(self): + """Test __len__ when _dirty_len is True and _pending_removals is not empty.""" + # Create a dictionary and add some items using proper weak reference keys + d = WeakIdKeyDictionary() + + # Create objects that can be weakly referenced + class TestObject: + pass + + key1 = TestObject() + key2 = TestObject() + + # Manually add to data dict using proper WeakIdRef keys + ref1 = WeakIdRef(key1, d._remove) + ref2 = WeakIdRef(key2, d._remove) + d.data[ref1] = "value1" + d.data[ref2] = "value2" + + # Force _dirty_len to True and add pending removals + d._dirty_len = True + d._pending_removals = [ref1, ref2] + + # Call __len__ which should trigger _scrub_removals + length = len(d) + + # Verify the result and that _scrub_removals was called + assert length == len(d.data) - len(d._pending_removals) + assert d._dirty_len is False # _scrub_removals should set this to False + + def test_len_with_dirty_len_and_empty_pending_removals(self): + """Test __len__ when _dirty_len is True but _pending_removals is empty.""" + d = WeakIdKeyDictionary() + + # Create an object that can be weakly referenced + class TestObject: + pass + + key = TestObject() + + # Manually add to data dict using proper WeakIdRef key + ref_key = WeakIdRef(key, d._remove) + d.data[ref_key] = "value" + + # Force _dirty_len to True but keep pending removals empty + d._dirty_len = True + d._pending_removals = [] + + # Call __len__ - should not trigger _scrub_removals since pending_removals is empty + length = len(d) + + # Verify the result + assert length == len(d.data) - len(d._pending_removals) + # _dirty_len should remain True since _scrub_removals wasn't called + assert d._dirty_len is True + + def test_len_without_dirty_len(self): + """Test __len__ when _dirty_len is False.""" + d = WeakIdKeyDictionary() + + # Create an object that can be weakly referenced + class TestObject: + pass + + key = TestObject() + + # Manually add to data dict using proper WeakIdRef key + ref_key = WeakIdRef(key, d._remove) + d.data[ref_key] = "value" + + # _dirty_len is False by default + d._dirty_len = False + d._pending_removals = [ref_key] # This shouldn't matter since _dirty_len is False + + # Call __len__ - should not trigger _scrub_removals + length = len(d) + + # Verify the result + assert length == len(d.data) - len(d._pending_removals) + assert d._dirty_len is False # Should remain unchanged diff --git a/tests/test_coverup_51.py b/tests/test_coverup_51.py new file mode 100644 index 0000000..f67ede1 --- /dev/null +++ b/tests/test_coverup_51.py @@ -0,0 +1,85 @@ +# file: objwatch/tracer.py:781-787 +# asked: {"lines": [781, 785, 786, 787], "branches": []} +# gained: {"lines": [781, 785, 786, 787], "branches": []} + +import pytest +import sys +from unittest.mock import Mock, patch +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerStop: + """Test cases for Tracer.stop() method to achieve full coverage.""" + + def test_stop_method_calls_sys_settrace_and_save_xml(self, monkeypatch): + """Test that stop() calls sys.settrace(None) and event_handlers.save_xml().""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = [] + mock_config.exclude_targets = [] + mock_config.output_xml = None + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + # Create tracer instance + tracer = Tracer(mock_config) + + # Mock the event_handlers.save_xml method + mock_save_xml = Mock() + tracer.event_handlers.save_xml = mock_save_xml + + # Mock sys.settrace to track calls + mock_settrace = Mock() + monkeypatch.setattr(sys, 'settrace', mock_settrace) + + # Mock log_info to avoid side effects + mock_log_info = Mock() + monkeypatch.setattr('objwatch.tracer.log_info', mock_log_info) + + # Call the stop method + tracer.stop() + + # Verify the method calls + mock_log_info.assert_called_once_with("Stopping tracing.") + mock_settrace.assert_called_once_with(None) + mock_save_xml.assert_called_once() + + def test_stop_method_with_xml_output(self, monkeypatch): + """Test stop() when XML output is configured.""" + # Create a mock config with XML output + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = [] + mock_config.exclude_targets = [] + mock_config.output_xml = "test_output.xml" + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + # Create tracer instance + tracer = Tracer(mock_config) + + # Mock the event_handlers.save_xml method + mock_save_xml = Mock() + tracer.event_handlers.save_xml = mock_save_xml + + # Mock sys.settrace to track calls + mock_settrace = Mock() + monkeypatch.setattr(sys, 'settrace', mock_settrace) + + # Mock log_info to avoid side effects + mock_log_info = Mock() + monkeypatch.setattr('objwatch.tracer.log_info', mock_log_info) + + # Call the stop method + tracer.stop() + + # Verify the method calls + mock_log_info.assert_called_once_with("Stopping tracing.") + mock_settrace.assert_called_once_with(None) + mock_save_xml.assert_called_once() diff --git a/tests/test_coverup_52.py b/tests/test_coverup_52.py new file mode 100644 index 0000000..ef21f91 --- /dev/null +++ b/tests/test_coverup_52.py @@ -0,0 +1,52 @@ +# file: objwatch/tracer.py:91-93 +# asked: {"lines": [91, 92, 93], "branches": []} +# gained: {"lines": [91, 92, 93], "branches": []} + +import pytest +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerCallDepth: + """Test cases for Tracer call_depth property.""" + + def test_call_depth_property_returns_initial_value(self): + """Test that call_depth property returns the initial _call_depth value.""" + # Arrange + config = ObjWatchConfig(targets=["some_module"]) + tracer = Tracer(config) + + # Act + result = tracer.call_depth + + # Assert + assert result == 0 + assert result == tracer._call_depth + + def test_call_depth_property_returns_updated_value(self): + """Test that call_depth property returns updated _call_depth value.""" + # Arrange + config = ObjWatchConfig(targets=["some_module"]) + tracer = Tracer(config) + tracer._call_depth = 5 + + # Act + result = tracer.call_depth + + # Assert + assert result == 5 + assert result == tracer._call_depth + + def test_call_depth_property_returns_negative_value(self): + """Test that call_depth property returns negative _call_depth value.""" + # Arrange + config = ObjWatchConfig(targets=["some_module"]) + tracer = Tracer(config) + tracer._call_depth = -3 + + # Act + result = tracer.call_depth + + # Assert + assert result == -3 + assert result == tracer._call_depth diff --git a/tests/test_coverup_53.py b/tests/test_coverup_53.py new file mode 100644 index 0000000..82653b9 --- /dev/null +++ b/tests/test_coverup_53.py @@ -0,0 +1,53 @@ +# file: objwatch/core.py:77-82 +# asked: {"lines": [77, 81, 82], "branches": []} +# gained: {"lines": [77, 81, 82], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.core import ObjWatch +from objwatch.tracer import Tracer +from objwatch.utils.logger import log_info + + +class TestObjWatchStop: + """Test cases for ObjWatch.stop() method to achieve full coverage.""" + + def test_stop_method_calls_tracer_stop(self, monkeypatch): + """Test that ObjWatch.stop() calls tracer.stop() and logs the stop message.""" + # Mock the tracer and log_info + mock_tracer = Mock(spec=Tracer) + mock_log_info = Mock() + + # Create ObjWatch instance with minimal configuration + objwatch = ObjWatch(targets=["some_module"]) + objwatch.tracer = mock_tracer + + # Patch log_info to verify it's called + monkeypatch.setattr("objwatch.core.log_info", mock_log_info) + + # Call the stop method + objwatch.stop() + + # Verify log_info was called with the expected message + mock_log_info.assert_called_once_with("Stopping ObjWatch tracing.") + + # Verify tracer.stop() was called + mock_tracer.stop.assert_called_once() + + def test_stop_method_with_context_manager_cleanup(self): + """Test that stop works correctly when used with context manager pattern.""" + # Create ObjWatch instance + objwatch = ObjWatch(targets=["some_module"]) + + # Mock the tracer + mock_tracer = Mock(spec=Tracer) + objwatch.tracer = mock_tracer + + # Mock log_info to avoid actual logging + with patch("objwatch.core.log_info") as mock_log_info: + # Call stop method + objwatch.stop() + + # Verify the calls + mock_log_info.assert_called_once_with("Stopping ObjWatch tracing.") + mock_tracer.stop.assert_called_once() diff --git a/tests/test_coverup_54.py b/tests/test_coverup_54.py new file mode 100644 index 0000000..cbdebcc --- /dev/null +++ b/tests/test_coverup_54.py @@ -0,0 +1,100 @@ +# file: objwatch/tracer.py:773-779 +# asked: {"lines": [773, 777, 778, 779], "branches": []} +# gained: {"lines": [773, 777, 778, 779], "branches": []} + +import pytest +import sys +from unittest.mock import Mock, patch, MagicMock +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerStart: + """Test cases for Tracer.start() method to achieve full coverage.""" + + def test_start_sets_trace_and_calls_sync(self, monkeypatch): + """Test that start() sets trace function and calls mp_handlers.sync()""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = [] + mock_config.exclude_targets = [] + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + # Create tracer instance + tracer = Tracer(mock_config) + + # Mock the trace_factory to return a dummy function + mock_trace_func = Mock() + tracer.trace_factory = Mock(return_value=mock_trace_func) + + # Mock sys.settrace and mp_handlers.sync + mock_settrace = Mock() + mock_sync = Mock() + + monkeypatch.setattr(sys, 'settrace', mock_settrace) + tracer.mp_handlers.sync = mock_sync + + # Mock log_info to verify it's called + mock_log_info = Mock() + monkeypatch.setattr('objwatch.tracer.log_info', mock_log_info) + + # Call the start method + tracer.start() + + # Verify the method calls + mock_log_info.assert_called_once_with("Starting tracing.") + tracer.trace_factory.assert_called_once() + mock_settrace.assert_called_once_with(mock_trace_func) + mock_sync.assert_called_once() + + def test_start_with_mock_trace_factory(self, monkeypatch): + """Test start() with a more realistic trace_factory mock""" + # Create a mock config + mock_config = Mock(spec=ObjWatchConfig) + mock_config.with_locals = False + mock_config.with_globals = False + mock_config.targets = [] + mock_config.exclude_targets = [] + mock_config.output_xml = False + mock_config.framework = None + mock_config.wrapper = None + mock_config.indexes = None + + # Create tracer instance + tracer = Tracer(mock_config) + + # Create a real trace function that we can verify gets set + def mock_trace_func(frame, event, arg): + return mock_trace_func + + tracer.trace_factory = Mock(return_value=mock_trace_func) + + # Track what gets passed to settrace + captured_trace_func = None + + def mock_settrace(func): + nonlocal captured_trace_func + captured_trace_func = func + + mock_sync = Mock() + + monkeypatch.setattr(sys, 'settrace', mock_settrace) + tracer.mp_handlers.sync = mock_sync + + # Mock log_info + mock_log_info = Mock() + monkeypatch.setattr('objwatch.tracer.log_info', mock_log_info) + + # Call the start method + tracer.start() + + # Verify the method calls + mock_log_info.assert_called_once_with("Starting tracing.") + tracer.trace_factory.assert_called_once() + assert captured_trace_func is mock_trace_func + mock_sync.assert_called_once() diff --git a/tests/test_coverup_55.py b/tests/test_coverup_55.py new file mode 100644 index 0000000..5778c21 --- /dev/null +++ b/tests/test_coverup_55.py @@ -0,0 +1,109 @@ +# file: objwatch/utils/weak.py:236-239 +# asked: {"lines": [236, 237, 238, 239], "branches": [[237, 238], [237, 239]]} +# gained: {"lines": [236, 237, 238, 239], "branches": [[237, 238], [237, 239]]} + +import pytest +from collections.abc import Mapping +from objwatch.utils.weak import WeakIdKeyDictionary + + +class TestWeakIdKeyDictionaryEq: + def test_eq_with_mapping_same_content(self): + """Test __eq__ returns True when comparing with another Mapping with same content""" + + # Create objects that can be weakly referenced + class TestObject: + def __init__(self, name): + self.name = name + + dict1 = WeakIdKeyDictionary() + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + dict1[obj1] = "value1" + dict1[obj2] = "value2" + + dict2 = WeakIdKeyDictionary() + dict2[obj1] = "value1" + dict2[obj2] = "value2" + + assert dict1 == dict2 + assert dict2 == dict1 + + def test_eq_with_mapping_different_content(self): + """Test __eq__ returns False when comparing with another Mapping with different content""" + + class TestObject: + def __init__(self, name): + self.name = name + + dict1 = WeakIdKeyDictionary() + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + dict1[obj1] = "value1" + dict1[obj2] = "value2" + + dict2 = WeakIdKeyDictionary() + dict2[obj1] = "value1" + dict2[obj2] = "different_value" + + assert not (dict1 == dict2) + assert not (dict2 == dict1) + + def test_eq_with_non_mapping_returns_notimplemented(self): + """Test __eq__ returns NotImplemented when comparing with non-Mapping object""" + + class TestObject: + def __init__(self, name): + self.name = name + + dict1 = WeakIdKeyDictionary() + obj1 = TestObject("obj1") + dict1[obj1] = "value1" + + # Test with non-Mapping object + result = dict1.__eq__("not_a_mapping") + assert result is NotImplemented + + # Test with integer + result = dict1.__eq__(42) + assert result is NotImplemented + + # Test with list + result = dict1.__eq__(["a", "b"]) + assert result is NotImplemented + + def test_eq_with_regular_dict_same_content(self): + """Test __eq__ works correctly with regular dict containing same objects""" + + class TestObject: + def __init__(self, name): + self.name = name + + dict1 = WeakIdKeyDictionary() + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + dict1[obj1] = "value1" + dict1[obj2] = "value2" + + regular_dict = {obj1: "value1", obj2: "value2"} + + assert dict1 == regular_dict + assert regular_dict == dict1 + + def test_eq_with_regular_dict_different_content(self): + """Test __eq__ returns False when comparing with regular dict with different content""" + + class TestObject: + def __init__(self, name): + self.name = name + + dict1 = WeakIdKeyDictionary() + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + dict1[obj1] = "value1" + dict1[obj2] = "value2" + + regular_dict = {obj1: "value1", obj2: "different_value"} + + assert not (dict1 == regular_dict) + assert not (regular_dict == dict1) diff --git a/tests/test_coverup_56.py b/tests/test_coverup_56.py new file mode 100644 index 0000000..98e573f --- /dev/null +++ b/tests/test_coverup_56.py @@ -0,0 +1,96 @@ +# file: objwatch/wrappers/tensor_shape_wrapper.py:41-54 +# asked: {"lines": [41, 52, 53, 54], "branches": []} +# gained: {"lines": [41, 52, 53, 54], "branches": []} + +import pytest +from types import FrameType +from unittest.mock import Mock, MagicMock +from objwatch.wrappers.tensor_shape_wrapper import TensorShapeWrapper + + +class TestTensorShapeWrapperWrapCall: + """Test cases for TensorShapeWrapper.wrap_call method.""" + + def test_wrap_call_with_positional_args(self, monkeypatch): + """Test wrap_call with positional arguments only.""" + wrapper = TensorShapeWrapper() + + # Mock frame with positional arguments + mock_frame = Mock(spec=FrameType) + mock_frame.f_code = Mock() + mock_frame.f_code.co_varnames = ('arg1', 'arg2') + mock_frame.f_code.co_argcount = 2 + mock_frame.f_locals = {'arg1': 'value1', 'arg2': 'value2'} + + # Mock the _extract_args_kwargs method to return specific args + monkeypatch.setattr(wrapper, '_extract_args_kwargs', lambda frame: (['value1', 'value2'], {})) + + # Mock the _format_args_kwargs method to return a formatted string + monkeypatch.setattr(wrapper, '_format_args_kwargs', lambda args, kwargs: "'0':'value1', '1':'value2'") + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == "'0':'value1', '1':'value2'" + + def test_wrap_call_with_keyword_args(self, monkeypatch): + """Test wrap_call with keyword arguments only.""" + wrapper = TensorShapeWrapper() + + # Mock frame with keyword arguments + mock_frame = Mock(spec=FrameType) + mock_frame.f_code = Mock() + mock_frame.f_code.co_varnames = () + mock_frame.f_code.co_argcount = 0 + mock_frame.f_locals = {'key1': 'val1', 'key2': 'val2'} + + # Mock the _extract_args_kwargs method to return specific kwargs + monkeypatch.setattr(wrapper, '_extract_args_kwargs', lambda frame: ([], {'key1': 'val1', 'key2': 'val2'})) + + # Mock the _format_args_kwargs method to return a formatted string + monkeypatch.setattr(wrapper, '_format_args_kwargs', lambda args, kwargs: "'key1':'val1', 'key2':'val2'") + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == "'key1':'val1', 'key2':'val2'" + + def test_wrap_call_with_mixed_args(self, monkeypatch): + """Test wrap_call with both positional and keyword arguments.""" + wrapper = TensorShapeWrapper() + + # Mock frame with mixed arguments + mock_frame = Mock(spec=FrameType) + mock_frame.f_code = Mock() + mock_frame.f_code.co_varnames = ('arg1',) + mock_frame.f_code.co_argcount = 1 + mock_frame.f_locals = {'arg1': 'pos_val', 'kwarg1': 'kw_val'} + + # Mock the _extract_args_kwargs method to return mixed args + monkeypatch.setattr(wrapper, '_extract_args_kwargs', lambda frame: (['pos_val'], {'kwarg1': 'kw_val'})) + + # Mock the _format_args_kwargs method to return a formatted string + monkeypatch.setattr(wrapper, '_format_args_kwargs', lambda args, kwargs: "'0':'pos_val', 'kwarg1':'kw_val'") + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == "'0':'pos_val', 'kwarg1':'kw_val'" + + def test_wrap_call_with_no_args(self, monkeypatch): + """Test wrap_call with no arguments.""" + wrapper = TensorShapeWrapper() + + # Mock frame with no arguments + mock_frame = Mock(spec=FrameType) + mock_frame.f_code = Mock() + mock_frame.f_code.co_varnames = () + mock_frame.f_code.co_argcount = 0 + mock_frame.f_locals = {} + + # Mock the _extract_args_kwargs method to return empty args + monkeypatch.setattr(wrapper, '_extract_args_kwargs', lambda frame: ([], {})) + + # Mock the _format_args_kwargs method to return empty string + monkeypatch.setattr(wrapper, '_format_args_kwargs', lambda args, kwargs: '') + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == '' diff --git a/tests/test_coverup_57.py b/tests/test_coverup_57.py new file mode 100644 index 0000000..a62ff1a --- /dev/null +++ b/tests/test_coverup_57.py @@ -0,0 +1,77 @@ +# file: objwatch/utils/weak.py:149-154 +# asked: {"lines": [149, 150, 151, 152, 153, 154], "branches": []} +# gained: {"lines": [149, 150, 151, 154], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryContains: + + def test_contains_with_valid_key(self, monkeypatch): + """Test __contains__ with a valid key that can be converted to WeakIdRef""" + + # Create a mock ref_type that returns a consistent reference + class MockRef: + def __init__(self, key): + self.key = key + + def __hash__(self): + return id(self.key) + + def __eq__(self, other): + return isinstance(other, MockRef) and self.key == other.key + + def mock_ref_type(key, callback=None): + return MockRef(key) + + d = WeakIdKeyDictionary() + monkeypatch.setattr(d, 'ref_type', mock_ref_type) + + # Add a key to the data directly to simulate the dictionary state + key = object() + mock_ref = mock_ref_type(key) + d.data[mock_ref] = "value" + + # This should execute lines 149-151 and 154 + # The key should be found because mock_ref_type(key) returns the same MockRef instance + assert key in d + + def test_contains_with_invalid_key_type(self): + """Test __contains__ with a key that raises TypeError when creating WeakIdRef""" + d = WeakIdKeyDictionary() + + # Create a key that would cause TypeError when passed to ref_type + # For example, using a type that cannot be weakly referenced + class UnhashableType: + __hash__ = None + + key = UnhashableType() + + # This should execute lines 149-153 (TypeError path) + assert key not in d + + def test_contains_with_nonexistent_valid_key(self, monkeypatch): + """Test __contains__ with a valid key that is not in the dictionary""" + + # Create a mock ref_type that returns a consistent reference + class MockRef: + def __init__(self, key): + self.key = key + + def __hash__(self): + return id(self.key) + + def __eq__(self, other): + return isinstance(other, MockRef) and self.key == other.key + + def mock_ref_type(key, callback=None): + return MockRef(key) + + d = WeakIdKeyDictionary() + monkeypatch.setattr(d, 'ref_type', mock_ref_type) + + key = object() + + # This should execute lines 149-151 and 154 (wr not in self.data) + assert key not in d diff --git a/tests/test_coverup_58.py b/tests/test_coverup_58.py new file mode 100644 index 0000000..170c322 --- /dev/null +++ b/tests/test_coverup_58.py @@ -0,0 +1,164 @@ +# file: objwatch/utils/weak.py:190-196 +# asked: {"lines": [190, 191, 192, 193, 194, 195, 196], "branches": [[192, 193], [195, 192], [195, 196]]} +# gained: {"lines": [190, 191, 192, 193, 194, 195, 196], "branches": [[192, 193], [195, 196]]} + +import pytest +import weakref +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryPopitem: + def test_popitem_returns_valid_object(self): + """Test that popitem returns a valid object when the weak reference is still alive.""" + + # Create an object that can be weakly referenced + class TestObject: + def __init__(self, value): + self.value = value + + obj = TestObject("test") + weak_dict = WeakIdKeyDictionary() + weak_dict[obj] = "test_value" + + # The object should still be alive, so popitem should return it + key, value = weak_dict.popitem() + assert key is obj + assert value == "test_value" + assert len(weak_dict) == 0 + + def test_popitem_skips_dead_references(self): + """Test that popitem skips dead references and continues until finding a valid one.""" + weak_dict = WeakIdKeyDictionary() + + # Create objects that can be weakly referenced + class TestObject: + pass + + # Create an object and add it to the dictionary + obj1 = TestObject() + weak_dict[obj1] = "value1" + + # Create another object and add it, then let it be garbage collected + obj2 = TestObject() + weak_dict[obj2] = "value2" + + # Remove the reference to obj2 so it becomes garbage collected + del obj2 + + # Force garbage collection to clean up the dead reference + import gc + + gc.collect() + + # Now popitem should skip the dead reference for obj2 and return obj1 + key, value = weak_dict.popitem() + assert key is obj1 + assert value == "value1" + assert len(weak_dict) == 0 + + def test_popitem_with_multiple_dead_references(self): + """Test that popitem handles multiple dead references before finding a valid one.""" + weak_dict = WeakIdKeyDictionary() + + # Create objects that can be weakly referenced + class TestObject: + pass + + # Create multiple objects that will become dead references + dead_objects = [] + for i in range(3): + obj = TestObject() + weak_dict[obj] = f"dead_value_{i}" + dead_objects.append(obj) + + # Create one valid object + valid_obj = TestObject() + weak_dict[valid_obj] = "valid_value" + + # Remove references to the dead objects + del dead_objects + + # Force garbage collection + import gc + + gc.collect() + + # popitem should skip all dead references and return the valid one + key, value = weak_dict.popitem() + assert key is valid_obj + assert value == "valid_value" + + # The dictionary should now be empty + # We need to manually check if there are any remaining items + # by calling popitem until we get an exception + try: + while True: + weak_dict.popitem() + except KeyError: + pass # Expected when dictionary is empty + + assert len(weak_dict) == 0 + + def test_popitem_sets_dirty_len_flag(self): + """Test that popitem sets the _dirty_len flag to True.""" + + # Create an object that can be weakly referenced + class TestObject: + pass + + obj = TestObject() + weak_dict = WeakIdKeyDictionary() + weak_dict[obj] = "test_value" + + # Initially, _dirty_len should be False + assert not weak_dict._dirty_len + + # After popitem, _dirty_len should be True + weak_dict.popitem() + assert weak_dict._dirty_len + + def test_popitem_cleans_up_dead_references_after_valid_pop(self): + """Test that popitem properly cleans up after finding a valid object.""" + weak_dict = WeakIdKeyDictionary() + + # Create objects that can be weakly referenced + class TestObject: + pass + + # Add some dead references + dead_obj1 = TestObject() + dead_obj2 = TestObject() + weak_dict[dead_obj1] = "dead1" + weak_dict[dead_obj2] = "dead2" + + # Add a valid object + valid_obj = TestObject() + weak_dict[valid_obj] = "valid" + + # Remove references to dead objects + del dead_obj1, dead_obj2 + + # Force garbage collection + import gc + + gc.collect() + + # popitem should return the valid object + key, value = weak_dict.popitem() + assert key is valid_obj + assert value == "valid" + + # The dictionary should be empty after popitem + # Check by trying to popitem again (should raise KeyError) + try: + weak_dict.popitem() + assert False, "popitem should have raised KeyError for empty dictionary" + except KeyError: + pass # Expected + + def test_popitem_empty_dictionary_raises_keyerror(self): + """Test that popitem raises KeyError when the dictionary is empty.""" + weak_dict = WeakIdKeyDictionary() + + with pytest.raises(KeyError): + weak_dict.popitem() diff --git a/tests/test_coverup_59.py b/tests/test_coverup_59.py new file mode 100644 index 0000000..dca4f66 --- /dev/null +++ b/tests/test_coverup_59.py @@ -0,0 +1,69 @@ +# file: objwatch/wrappers/abc_wrapper.py:36-48 +# asked: {"lines": [36, 37, 48], "branches": []} +# gained: {"lines": [36, 37], "branches": []} + +import pytest +from typing import Any +from objwatch.wrappers.abc_wrapper import ABCWrapper + + +class TestABCWrapper(ABCWrapper): + """Concrete implementation for testing abstract methods.""" + + def wrap_call(self, func_name: str, frame: Any) -> str: + return f"call:{func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + return f"return:{func_name}:{result}" + + def wrap_upd(self, old_value: Any, current_value: Any) -> tuple: + return f"old:{old_value}", f"new:{current_value}" + + +def test_abc_wrapper_cannot_be_instantiated_directly(): + """Test that ABCWrapper cannot be instantiated directly.""" + with pytest.raises(TypeError): + ABCWrapper() + + +def test_concrete_implementation_can_be_instantiated(): + """Test that concrete implementation can be instantiated.""" + wrapper = TestABCWrapper() + assert wrapper is not None + + +def test_wrap_return_abstract_method(): + """Test that wrap_return is an abstract method requiring implementation.""" + assert hasattr(ABCWrapper, 'wrap_return') + assert ABCWrapper.wrap_return.__isabstractmethod__ + + +def test_concrete_wrap_return_implementation(): + """Test concrete implementation of wrap_return method.""" + wrapper = TestABCWrapper() + result = wrapper.wrap_return("test_func", "test_result") + assert result == "return:test_func:test_result" + assert isinstance(result, str) + + +def test_wrap_return_with_none_result(): + """Test wrap_return with None result.""" + wrapper = TestABCWrapper() + result = wrapper.wrap_return("none_func", None) + assert result == "return:none_func:None" + + +def test_wrap_return_with_numeric_result(): + """Test wrap_return with numeric result.""" + wrapper = TestABCWrapper() + result = wrapper.wrap_return("math_func", 42) + assert result == "return:math_func:42" + + +def test_wrap_return_with_complex_result(): + """Test wrap_return with complex data structure.""" + wrapper = TestABCWrapper() + complex_data = {"key": "value", "list": [1, 2, 3]} + result = wrapper.wrap_return("complex_func", complex_data) + assert "complex_func" in result + assert isinstance(result, str) diff --git a/tests/test_coverup_6.py b/tests/test_coverup_6.py new file mode 100644 index 0000000..dc82286 --- /dev/null +++ b/tests/test_coverup_6.py @@ -0,0 +1,78 @@ +# file: objwatch/utils/weak.py:18-40 +# asked: {"lines": [18, 24, 26, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40], "branches": [[30, 31], [30, 32], [36, 0], [36, 37], [39, 0], [39, 40]]} +# gained: {"lines": [18, 24, 26, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40], "branches": [[30, 31], [30, 32], [36, 0], [36, 37], [39, 0], [39, 40]]} + +import pytest +from weakref import ref +from objwatch.utils.weak import _IterationGuard + + +class MockWeakContainer: + def __init__(self): + self._iterating = set() + self._commit_removals_called = False + + def _commit_removals(self): + self._commit_removals_called = True + + +def test_iteration_guard_context_manager(): + """Test _IterationGuard context manager functionality.""" + container = MockWeakContainer() + guard = _IterationGuard(container) + + # Test __enter__ adds self to container's _iterating set + with guard: + assert guard in container._iterating + + # Test __exit__ removes self from container's _iterating set + assert guard not in container._iterating + # _commit_removals should be called when set becomes empty + assert container._commit_removals_called + + +def test_iteration_guard_multiple_guards(): + """Test _IterationGuard with multiple guards in container.""" + container = MockWeakContainer() + guard1 = _IterationGuard(container) + guard2 = _IterationGuard(container) + + # Enter first guard + with guard1: + assert guard1 in container._iterating + assert len(container._iterating) == 1 + assert not container._commit_removals_called + + # Enter second guard + with guard2: + assert guard2 in container._iterating + assert len(container._iterating) == 2 + assert not container._commit_removals_called + + # After exiting second guard, first guard should still be there + assert guard1 in container._iterating + assert guard2 not in container._iterating + assert len(container._iterating) == 1 + assert not container._commit_removals_called + + # After exiting first guard, set should be empty and _commit_removals called + assert len(container._iterating) == 0 + assert container._commit_removals_called + + +def test_iteration_guard_with_none_container(): + """Test _IterationGuard when weakcontainer becomes None.""" + container = MockWeakContainer() + guard = _IterationGuard(container) + + # Simulate container being garbage collected + weak_ref = guard.weakcontainer + del container + + # Context manager should handle None container gracefully + with guard: + # __enter__ should handle None container + pass + + # __exit__ should handle None container + assert not hasattr(guard, '_iterating') or guard not in getattr(guard, '_iterating', set()) diff --git a/tests/test_coverup_60.py b/tests/test_coverup_60.py new file mode 100644 index 0000000..ec0ee8e --- /dev/null +++ b/tests/test_coverup_60.py @@ -0,0 +1,96 @@ +# file: objwatch/wrappers/abc_wrapper.py:86-100 +# asked: {"lines": [86, 97, 98, 99, 100], "branches": []} +# gained: {"lines": [86, 97, 98, 99, 100], "branches": []} + +import pytest +from typing import Any, List +from unittest.mock import Mock, patch +from objwatch.wrappers.abc_wrapper import ABCWrapper + + +class ConcreteTestWrapper(ABCWrapper): + """Concrete implementation of ABCWrapper for testing purposes.""" + + def __init__(self): + super().__init__() + + def wrap_call(self, func_name: str, frame) -> str: + return f"call_{func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + return f"return_{func_name}_{result}" + + def wrap_upd(self, old_value: Any, current_value: Any): + return f"old_{old_value}", f"current_{current_value}" + + def _format_value(self, value: Any, is_return: bool = False) -> str: + """Mock implementation for testing.""" + if isinstance(value, (int, float, str, bool)): + return str(value) + elif isinstance(value, (list, tuple)): + return f"sequence_{len(value)}" + else: + return f"type_{type(value).__name__}" + + +class TestABCWrapperFormatArgsKwargs: + """Test cases for ABCWrapper._format_args_kwargs method.""" + + def test_format_args_kwargs_with_positional_args_only(self): + """Test _format_args_kwargs with only positional arguments.""" + wrapper = ConcreteTestWrapper() + + args = [1, "hello", True] + kwargs = {} + + result = wrapper._format_args_kwargs(args, kwargs) + + expected = "'0':1, '1':hello, '2':True" + assert result == expected + + def test_format_args_kwargs_with_kwargs_only(self): + """Test _format_args_kwargs with only keyword arguments.""" + wrapper = ConcreteTestWrapper() + + args = [] + kwargs = {"param1": 42, "param2": "world"} + + result = wrapper._format_args_kwargs(args, kwargs) + + expected = "'param1':42, 'param2':world" + assert result == expected + + def test_format_args_kwargs_with_mixed_args_and_kwargs(self): + """Test _format_args_kwargs with both positional and keyword arguments.""" + wrapper = ConcreteTestWrapper() + + args = [3.14, "test"] + kwargs = {"key1": 100, "key2": False} + + result = wrapper._format_args_kwargs(args, kwargs) + + expected = "'0':3.14, '1':test, 'key1':100, 'key2':False" + assert result == expected + + def test_format_args_kwargs_with_complex_types(self): + """Test _format_args_kwargs with complex data types.""" + wrapper = ConcreteTestWrapper() + + args = [[1, 2, 3], {"nested": "dict"}] + kwargs = {"complex_param": (4, 5, 6)} + + result = wrapper._format_args_kwargs(args, kwargs) + + expected = "'0':sequence_3, '1':type_dict, 'complex_param':sequence_3" + assert result == expected + + def test_format_args_kwargs_empty(self): + """Test _format_args_kwargs with empty arguments.""" + wrapper = ConcreteTestWrapper() + + args = [] + kwargs = {} + + result = wrapper._format_args_kwargs(args, kwargs) + + assert result == "" diff --git a/tests/test_coverup_61.py b/tests/test_coverup_61.py new file mode 100644 index 0000000..8ffa2c8 --- /dev/null +++ b/tests/test_coverup_61.py @@ -0,0 +1,167 @@ +# file: objwatch/wrappers/abc_wrapper.py:102-131 +# asked: {"lines": [102, 113, 114, 115, 116, 117, 118, 120, 122, 123, 124, 125, 127, 128, 129, 130, 131], "branches": [[113, 114], [113, 115], [115, 116], [115, 122], [117, 118], [117, 120], [127, 128], [127, 131], [128, 129], [128, 130]]} +# gained: {"lines": [102, 113, 114, 115, 116, 117, 118, 120, 122, 123, 124, 125, 127, 128, 129, 130, 131], "branches": [[113, 114], [113, 115], [115, 116], [115, 122], [117, 118], [117, 120], [127, 128], [127, 131], [128, 129], [128, 130]]} + +import pytest +from typing import Any +from unittest.mock import Mock +from objwatch.wrappers.abc_wrapper import ABCWrapper +from objwatch.constants import Constants +from objwatch.event_handls import EventHandls +from enum import Enum +from types import FunctionType, FrameType + + +class TestEnum(Enum): + VALUE1 = 1 + VALUE2 = 2 + + +class ConcreteTestWrapper(ABCWrapper): + def wrap_call(self, func_name: str, frame: FrameType) -> str: + return f"call:{func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + return f"return:{func_name}:{result}" + + def wrap_upd(self, old_value: Any, current_value: Any) -> tuple: + return f"old:{old_value}", f"new:{current_value}" + + +class TestABCWrapperFormatValue: + + def test_format_value_log_element_types(self): + wrapper = ConcreteTestWrapper() + + # Test bool + result = wrapper._format_value(True) + assert result == "True" + + # Test int + result = wrapper._format_value(42) + assert result == "42" + + # Test float + result = wrapper._format_value(3.14) + assert result == "3.14" + + # Test str + result = wrapper._format_value("hello") + assert result == "hello" + + # Test None + result = wrapper._format_value(None) + assert result == "None" + + # Test FunctionType + def test_func(): + pass + + result = wrapper._format_value(test_func) + assert result == f"{test_func}" + + # Test Enum + result = wrapper._format_value(TestEnum.VALUE1) + assert result == f"{TestEnum.VALUE1}" + + def test_format_value_log_sequence_types_with_formatted_sequence(self, monkeypatch): + wrapper = ConcreteTestWrapper() + + # Mock format_sequence to return a non-empty string + mock_format_sequence = Mock(return_value="[1, 2, 3]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + # Test list + result = wrapper._format_value([1, 2, 3]) + assert result == "[1, 2, 3]" + mock_format_sequence.assert_called_once_with([1, 2, 3], func=None) + + def test_format_value_log_sequence_types_with_empty_formatted_sequence(self, monkeypatch): + wrapper = ConcreteTestWrapper() + + # Mock format_sequence to return empty string + mock_format_sequence = Mock(return_value="") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + # Test tuple + result = wrapper._format_value((1, 2, 3)) + assert result == "(type)tuple" + mock_format_sequence.assert_called_once_with((1, 2, 3), func=None) + + def test_format_value_other_types_with_name_attribute(self): + wrapper = ConcreteTestWrapper() + + class TestClass: + __name__ = "TestClass" + + result = wrapper._format_value(TestClass) + assert result == "(type)TestClass" + + def test_format_value_other_types_without_name_attribute(self): + wrapper = ConcreteTestWrapper() + + # Create an object that doesn't have __name__ attribute + # Use a simple object instance instead of a class + test_obj = object() + + result = wrapper._format_value(test_obj) + assert result == "(type)object" + + def test_format_value_is_return_true_with_sequence_and_formatted(self, monkeypatch): + wrapper = ConcreteTestWrapper() + + # Mock format_sequence to return a non-empty string + mock_format_sequence = Mock(return_value="[1, 2, 3]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + # Test with is_return=True for sequence type + result = wrapper._format_value([1, 2, 3], is_return=True) + assert result == "[[1, 2, 3]]" + mock_format_sequence.assert_called_once_with([1, 2, 3], func=None) + + def test_format_value_is_return_true_with_sequence_and_empty_formatted(self, monkeypatch): + wrapper = ConcreteTestWrapper() + + # Mock format_sequence to return empty string + mock_format_sequence = Mock(return_value="") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + # Test with is_return=True for sequence type with empty formatted result + result = wrapper._format_value([1, 2, 3], is_return=True) + assert result == "[(type)list]" + mock_format_sequence.assert_called_once_with([1, 2, 3], func=None) + + def test_format_value_is_return_true_with_non_sequence(self): + wrapper = ConcreteTestWrapper() + + # Test with is_return=True for non-sequence type + result = wrapper._format_value(42, is_return=True) + assert result == "42" + + def test_format_value_is_return_false_with_sequence_and_formatted(self, monkeypatch): + wrapper = ConcreteTestWrapper() + + # Mock format_sequence to return a non-empty string + mock_format_sequence = Mock(return_value="[1, 2, 3]") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + # Test with is_return=False for sequence type (default) + result = wrapper._format_value([1, 2, 3], is_return=False) + assert result == "[1, 2, 3]" + mock_format_sequence.assert_called_once_with([1, 2, 3], func=None) + + def test_format_value_with_custom_format_sequence_func(self, monkeypatch): + wrapper = ConcreteTestWrapper() + + def custom_format_func(seq): + return ["custom"] * len(seq) + + wrapper.format_sequence_func = custom_format_func + + # Mock format_sequence to verify it uses the custom function + mock_format_sequence = Mock(return_value="['custom', 'custom', 'custom']") + monkeypatch.setattr(EventHandls, "format_sequence", mock_format_sequence) + + result = wrapper._format_value([1, 2, 3]) + assert result == "['custom', 'custom', 'custom']" + mock_format_sequence.assert_called_once_with([1, 2, 3], func=custom_format_func) diff --git a/tests/test_coverup_62.py b/tests/test_coverup_62.py new file mode 100644 index 0000000..a460740 --- /dev/null +++ b/tests/test_coverup_62.py @@ -0,0 +1,81 @@ +# file: objwatch/targets.py:21-34 +# asked: {"lines": [21, 32, 33, 34], "branches": [[32, 0], [32, 33]]} +# gained: {"lines": [21, 32, 33, 34], "branches": [[32, 0], [32, 33]]} + +import pytest +import ast +from objwatch.targets import iter_parents + + +class MockNode: + def __init__(self, parent=None): + self.parent = parent + + +def test_iter_parents_single_parent(): + """Test iter_parents with one parent node.""" + child = MockNode() + parent = MockNode() + child.parent = parent + + parents = list(iter_parents(child)) + # The function yields until parent is None + assert len(parents) == 2 + assert parents[0] == parent + assert parents[1] is None + + +def test_iter_parents_multiple_parents(): + """Test iter_parents with multiple parent nodes in chain.""" + grandchild = MockNode() + child = MockNode() + parent = MockNode() + grandparent = MockNode() + + grandchild.parent = child + child.parent = parent + parent.parent = grandparent + + parents = list(iter_parents(grandchild)) + assert len(parents) == 4 + assert parents[0] == child + assert parents[1] == parent + assert parents[2] == grandparent + assert parents[3] is None + + +def test_iter_parents_no_parent(): + """Test iter_parents with node that has no parent attribute.""" + node_without_parent = object() + + parents = list(iter_parents(node_without_parent)) + assert parents == [] + + +def test_iter_parents_parent_is_none(): + """Test iter_parents with node that has parent attribute set to None.""" + node = MockNode(parent=None) + + parents = list(iter_parents(node)) + assert len(parents) == 1 + assert parents[0] is None + + +def test_iter_parents_generator_behavior(): + """Test that iter_parents returns a generator and can be used in loops.""" + child = MockNode() + parent = MockNode() + grandparent = MockNode() + + child.parent = parent + parent.parent = grandparent + + # Test generator behavior + generator = iter_parents(child) + assert next(generator) == parent + assert next(generator) == grandparent + assert next(generator) is None + + # Test StopIteration + with pytest.raises(StopIteration): + next(generator) diff --git a/tests/test_coverup_63.py b/tests/test_coverup_63.py new file mode 100644 index 0000000..4e17d63 --- /dev/null +++ b/tests/test_coverup_63.py @@ -0,0 +1,174 @@ +# file: objwatch/targets.py:489-515 +# asked: {"lines": [489, 503, 504, 505, 506, 507, 508, 510, 511, 512, 513, 515], "branches": [[504, 505], [504, 506], [506, 507], [506, 508], [510, 511], [510, 515]]} +# gained: {"lines": [489, 503, 504, 505, 506, 507, 508, 510, 511, 512, 513, 515], "branches": [[504, 505], [504, 506], [506, 507], [506, 508], [510, 511], [510, 515]]} + +import pytest +import json +from unittest.mock import Mock, patch +from objwatch.targets import Targets +from objwatch.constants import Constants + + +class TestTargetsSerialize: + """Test cases for Targets.serialize_targets method.""" + + def test_serialize_targets_with_sets(self): + """Test serialization of targets containing sets.""" + # Create a mock targets structure that simulates processed targets + # The serialize_targets method works on self.targets, which is the processed structure + mock_targets = { + 'module1': { + 'classes': {'TestClass': {'methods': ['method1', 'method2']}}, + 'functions': ['func1', 'func2'], + 'globals': ['var1', 'var2'], + } + } + + # Create Targets instance and directly set the targets attribute + targets = Targets([]) # Empty initial targets + targets.targets = mock_targets + + result = targets.serialize_targets() + parsed = json.loads(result) + + # Verify the structure is properly serialized + assert 'module1' in parsed + assert parsed['module1']['classes'] == {'TestClass': {'methods': ['method1', 'method2']}} + assert parsed['module1']['functions'] == ['func1', 'func2'] + assert parsed['module1']['globals'] == ['var1', 'var2'] + + def test_serialize_targets_with_custom_objects(self): + """Test serialization of targets containing custom objects.""" + + class CustomObject: + def __init__(self): + self.value = 42 + self.name = "test" + + custom_obj = CustomObject() + + # Create a targets structure with custom objects + mock_targets = {'test_module': {'custom_obj': custom_obj, 'regular_data': {'a': 1, 'b': 2}}} + + targets = Targets([]) + targets.targets = mock_targets + + result = targets.serialize_targets() + parsed = json.loads(result) + + # Verify custom objects are converted to their __dict__ + assert parsed['test_module']['custom_obj'] == {'value': 42, 'name': 'test'} + assert parsed['test_module']['regular_data'] == {'a': 1, 'b': 2} + + def test_serialize_targets_with_objects_without_dict(self): + """Test serialization of objects without __dict__ attribute.""" + + # Create an object that doesn't have __dict__ but can be stringified + class NoDictObject: + __slots__ = ['value'] + + def __init__(self): + self.value = 100 + + def __str__(self): + return f"NoDictObject(value={self.value})" + + no_dict_obj = NoDictObject() + + mock_targets = {'module': {'no_dict': no_dict_obj, 'regular': 'data'}} + + targets = Targets([]) + targets.targets = mock_targets + + result = targets.serialize_targets() + parsed = json.loads(result) + + # Verify objects without __dict__ are converted to string + assert parsed['module']['no_dict'] == "NoDictObject(value=100)" + assert parsed['module']['regular'] == 'data' + + def test_serialize_targets_truncation_scenario(self): + """Test serialization when targets exceed MAX_TARGETS_DISPLAY limit.""" + # Create more module targets than the display limit + many_modules = {f'module_{i}': {'functions': [f'func_{i}']} for i in range(Constants.MAX_TARGETS_DISPLAY + 1)} + + targets = Targets([]) + targets.targets = many_modules + + result = targets.serialize_targets() + parsed = json.loads(result) + + # Verify truncation occurred - the warning message is added as an additional key + assert "Warning: too many top-level keys, only showing values like" in parsed + + # Count how many module keys are present (excluding the warning) + module_keys = [k for k in parsed.keys() if k.startswith('module_')] + + # The actual behavior shows all original keys are preserved but values are truncated + # Let's verify the behavior we're actually seeing + assert len(parsed) == Constants.MAX_TARGETS_DISPLAY + 1 + 1 # original keys + warning + assert len(module_keys) == Constants.MAX_TARGETS_DISPLAY + 1 + + # Verify all module values are truncated to "..." + for key in module_keys: + assert parsed[key] == "..." + + def test_serialize_targets_with_indent_parameter(self): + """Test serialization with custom indent parameter.""" + mock_targets = {'module_a': {'classes': {'ClassA': {'methods': ['method1']}}, 'functions': ['func_a']}} + + targets = Targets([]) + targets.targets = mock_targets + + custom_indent = 4 + result = targets.serialize_targets(indent=custom_indent) + + # Verify custom indent is used by checking the structure + parsed = json.loads(result) + expected = {'module_a': {'classes': {'ClassA': {'methods': ['method1']}}, 'functions': ['func_a']}} + assert parsed == expected + + # Also verify the string has proper indentation by checking newlines + lines = result.split('\n') + if len(lines) > 1: + # Check that second line has the expected indentation + second_line = lines[1] + leading_spaces = len(second_line) - len(second_line.lstrip()) + assert leading_spaces == custom_indent + + def test_serialize_targets_mixed_types(self): + """Test serialization with mixed data types including edge cases.""" + + class MixedObject: + def __init__(self): + self.nested_set = {10, 20} + self.regular_value = "test" + + mixed_obj = MixedObject() + + mock_targets = { + 'module1': { + 'set_data': {1, 2, 3}, + 'custom_obj': mixed_obj, + 'string_data': 'hello', + 'number_data': 42, + 'list_data': [4, 5, 6], + 'none_data': None, + 'bool_data': True, + } + } + + targets = Targets([]) + targets.targets = mock_targets + + result = targets.serialize_targets() + parsed = json.loads(result) + + # Verify all types are handled correctly + assert set(parsed['module1']['set_data']) == {1, 2, 3} + assert parsed['module1']['custom_obj'] == {'nested_set': [10, 20], 'regular_value': 'test'} + assert parsed['module1']['string_data'] == 'hello' + assert parsed['module1']['number_data'] == 42 + assert parsed['module1']['list_data'] == [4, 5, 6] + assert parsed['module1']['none_data'] is None + assert parsed['module1']['bool_data'] is True diff --git a/tests/test_coverup_64.py b/tests/test_coverup_64.py new file mode 100644 index 0000000..774e106 --- /dev/null +++ b/tests/test_coverup_64.py @@ -0,0 +1,249 @@ +# file: objwatch/tracer.py:494-558 +# asked: {"lines": [494, 496, 497, 498, 499, 500, 501, 502, 503, 516, 517, 518, 519, 520, 523, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557], "branches": [[516, 517], [516, 523], [525, 526], [525, 548], [526, 527], [526, 537], [537, 0], [537, 538], [548, 0], [548, 549]]} +# gained: {"lines": [494, 496, 497, 498, 499, 500, 501, 502, 503, 516, 517, 518, 519, 523, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557], "branches": [[516, 517], [516, 523], [525, 526], [525, 548], [526, 527], [526, 537], [537, 0], [537, 538], [548, 549]]} + +import pytest +from unittest.mock import Mock, MagicMock, patch +from objwatch.tracer import Tracer +from objwatch.events import EventType +from objwatch.config import ObjWatchConfig + + +class TestTracerHandleChangeType: + """Test cases for Tracer._handle_change_type method to achieve full coverage.""" + + def test_handle_change_type_with_lengths_apd_same_id(self, monkeypatch): + """Test _handle_change_type with lengths, APD change type, and same object IDs.""" + config = ObjWatchConfig(targets=["__main__"]) + tracer = Tracer(config) + + # Mock event handlers + mock_handlers = Mock() + mock_handlers.determine_change_type.return_value = EventType.APD + tracer.event_handlers = mock_handlers + + # Mock call_depth and index_info + tracer.call_depth = 1 + tracer.index_info = "test_index" + + # Create same object (same ID) but modify it to simulate append + test_list = [1, 2] + old_value = test_list + test_list.append(3) # Now test_list is [1, 2, 3] but same object ID + + # Call the method with same object IDs and lengths that result in APD + tracer._handle_change_type( + lineno=10, + class_name="TestClass", + key="test_key", + old_value=old_value, + current_value=test_list, # Same object ID but different content + old_value_len=2, + current_value_len=3, + ) + + # Verify APD handler was called + mock_handlers.handle_apd.assert_called_once_with(10, "TestClass", "test_key", list, 2, 3, 1, "test_index") + + def test_handle_change_type_with_lengths_pop_same_id(self, monkeypatch): + """Test _handle_change_type with lengths, POP change type, and same object IDs.""" + config = ObjWatchConfig(targets=["__main__"]) + tracer = Tracer(config) + + # Mock event handlers + mock_handlers = Mock() + mock_handlers.determine_change_type.return_value = EventType.POP + tracer.event_handlers = mock_handlers + + # Mock call_depth and index_info + tracer.call_depth = 1 + tracer.index_info = "test_index" + + # Create same object (same ID) but modify it to simulate pop + test_list = [1, 2, 3] + old_value = test_list + test_list.pop() # Now test_list is [1, 2] but same object ID + + # Call the method with same object IDs and lengths that result in POP + tracer._handle_change_type( + lineno=20, + class_name="TestClass", + key="test_key", + old_value=old_value, + current_value=test_list, # Same object ID but different content + old_value_len=3, + current_value_len=2, + ) + + # Verify POP handler was called + mock_handlers.handle_pop.assert_called_once_with(20, "TestClass", "test_key", list, 3, 2, 1, "test_index") + + def test_handle_change_type_with_lengths_upd_different_id(self, monkeypatch): + """Test _handle_change_type with lengths, UPD change type, and different object IDs.""" + config = ObjWatchConfig(targets=["__main__"]) + tracer = Tracer(config) + + # Mock event handlers + mock_handlers = Mock() + mock_handlers.determine_change_type.return_value = EventType.UPD + tracer.event_handlers = mock_handlers + + # Mock call_depth, index_info, and abc_wrapper + tracer.call_depth = 1 + tracer.index_info = "test_index" + tracer.abc_wrapper = Mock() + + # Create different objects (different IDs) + old_val = [1, 2] + current_val = [3, 4] + + # Call the method with different object IDs and UPD change type + tracer._handle_change_type( + lineno=30, + class_name="TestClass", + key="test_key", + old_value=old_val, + current_value=current_val, + old_value_len=2, + current_value_len=2, + ) + + # Verify UPD handler was called + mock_handlers.handle_upd.assert_called_once_with( + 30, "TestClass", "test_key", old_val, current_val, 1, "test_index", tracer.abc_wrapper + ) + + def test_handle_change_type_without_lengths_upd_different_id(self, monkeypatch): + """Test _handle_change_type without lengths, UPD change type, and different object IDs.""" + config = ObjWatchConfig(targets=["__main__"]) + tracer = Tracer(config) + + # Mock event handlers + mock_handlers = Mock() + tracer.event_handlers = mock_handlers + + # Mock call_depth, index_info, and abc_wrapper + tracer.call_depth = 1 + tracer.index_info = "test_index" + tracer.abc_wrapper = Mock() + + # Create different objects (different IDs) + old_val = {"a": 1} + current_val = {"b": 2} + + # Call the method with different object IDs and no lengths (should default to UPD) + tracer._handle_change_type( + lineno=40, + class_name="TestClass", + key="test_key", + old_value=old_val, + current_value=current_val, + old_value_len=None, + current_value_len=None, + ) + + # Verify UPD handler was called + mock_handlers.handle_upd.assert_called_once_with( + 40, "TestClass", "test_key", old_val, current_val, 1, "test_index", tracer.abc_wrapper + ) + + def test_handle_change_type_with_lengths_none_change_same_id(self, monkeypatch): + """Test _handle_change_type with lengths, None change type, and same object IDs.""" + config = ObjWatchConfig(targets=["__main__"]) + tracer = Tracer(config) + + # Mock event handlers + mock_handlers = Mock() + mock_handlers.determine_change_type.return_value = None + tracer.event_handlers = mock_handlers + + # Mock call_depth and index_info + tracer.call_depth = 1 + tracer.index_info = "test_index" + + # Create same object (same ID) with no length change + test_list = [1, 2] + old_value = test_list + + # Call the method with same object IDs and lengths that result in None change type + tracer._handle_change_type( + lineno=50, + class_name="TestClass", + key="test_key", + old_value=old_value, + current_value=test_list, # Same object, same length + old_value_len=2, + current_value_len=2, + ) + + # Verify no handlers were called (None change type with same IDs) + mock_handlers.handle_apd.assert_not_called() + mock_handlers.handle_pop.assert_not_called() + mock_handlers.handle_upd.assert_not_called() + + def test_handle_change_type_with_lengths_upd_same_id(self, monkeypatch): + """Test _handle_change_type with lengths, UPD change type, and same object IDs.""" + config = ObjWatchConfig(targets=["__main__"]) + tracer = Tracer(config) + + # Mock event handlers + mock_handlers = Mock() + mock_handlers.determine_change_type.return_value = EventType.UPD + tracer.event_handlers = mock_handlers + + # Mock call_depth and index_info + tracer.call_depth = 1 + tracer.index_info = "test_index" + + # Create same object (same ID) + test_list = [1, 2] + old_value = test_list + + # Call the method with same object IDs and UPD change type + tracer._handle_change_type( + lineno=60, + class_name="TestClass", + key="test_key", + old_value=old_value, + current_value=test_list, # Same object + old_value_len=2, + current_value_len=2, + ) + + # Verify no handlers were called (UPD change type with same IDs) + mock_handlers.handle_apd.assert_not_called() + mock_handlers.handle_pop.assert_not_called() + mock_handlers.handle_upd.assert_not_called() + + def test_handle_change_type_without_lengths_same_id(self, monkeypatch): + """Test _handle_change_type without lengths and same object IDs.""" + config = ObjWatchConfig(targets=["__main__"]) + tracer = Tracer(config) + + # Mock event handlers + mock_handlers = Mock() + tracer.event_handlers = mock_handlers + + # Mock call_depth and index_info + tracer.call_depth = 1 + tracer.index_info = "test_index" + + # Create same object (same ID) + test_dict = {"a": 1} + old_value = test_dict + + # Call the method with same object IDs and no lengths + tracer._handle_change_type( + lineno=70, + class_name="TestClass", + key="test_key", + old_value=old_value, + current_value=test_dict, # Same object + old_value_len=None, + current_value_len=None, + ) + + # Verify no handlers were called (no lengths with same IDs) + mock_handlers.handle_apd.assert_not_called() + mock_handlers.handle_pop.assert_not_called() + mock_handlers.handle_upd.assert_not_called() diff --git a/tests/test_coverup_65.py b/tests/test_coverup_65.py new file mode 100644 index 0000000..2fbcc1e --- /dev/null +++ b/tests/test_coverup_65.py @@ -0,0 +1,102 @@ +# file: objwatch/wrappers/abc_wrapper.py:50-62 +# asked: {"lines": [50, 51, 62], "branches": []} +# gained: {"lines": [50, 51], "branches": []} + +import pytest +from typing import Any, Tuple +from abc import ABC, abstractmethod +from objwatch.wrappers.abc_wrapper import ABCWrapper + + +class ConcreteWrapper(ABCWrapper): + """Concrete implementation for testing abstract methods.""" + + def wrap_call(self, func_name: str, frame: Any) -> str: + return f"call:{func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + return f"return:{func_name}:{result}" + + def wrap_upd(self, old_value: Any, current_value: Any) -> Tuple[str, str]: + return (f"old:{old_value}", f"new:{current_value}") + + +class FailingWrapper(ABCWrapper): + """Wrapper that fails to implement abstract methods for testing instantiation.""" + + def wrap_call(self, func_name: str, frame: Any) -> str: + return f"call:{func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + return f"return:{func_name}:{result}" + + +def test_abc_wrapper_cannot_be_instantiated(): + """Test that ABCWrapper cannot be instantiated directly.""" + with pytest.raises(TypeError): + ABCWrapper() + + +def test_concrete_wrapper_implements_all_abstract_methods(): + """Test that concrete wrapper properly implements all abstract methods.""" + wrapper = ConcreteWrapper() + + # Test wrap_upd method specifically + old_val, new_val = wrapper.wrap_upd("old_value", "new_value") + assert old_val == "old:old_value" + assert new_val == "new:new_value" + + # Test other abstract methods + assert wrapper.wrap_call("test_func", None) == "call:test_func" + assert wrapper.wrap_return("test_func", "result") == "return:test_func:result" + + +def test_incomplete_wrapper_fails_instantiation(): + """Test that wrapper missing wrap_upd method cannot be instantiated.""" + with pytest.raises(TypeError): + FailingWrapper() + + +def test_wrap_upd_with_different_data_types(): + """Test wrap_upd method with various data types.""" + wrapper = ConcreteWrapper() + + # Test with integers + old_int, new_int = wrapper.wrap_upd(1, 2) + assert old_int == "old:1" + assert new_int == "new:2" + + # Test with strings + old_str, new_str = wrapper.wrap_upd("hello", "world") + assert old_str == "old:hello" + assert new_str == "new:world" + + # Test with None values + old_none, new_none = wrapper.wrap_upd(None, "something") + assert old_none == "old:None" + assert new_none == "new:something" + + # Test with lists + old_list, new_list = wrapper.wrap_upd([1, 2], [3, 4]) + assert old_list == "old:[1, 2]" + assert new_list == "new:[3, 4]" + + +def test_wrap_upd_edge_cases(): + """Test wrap_upd method with edge cases.""" + wrapper = ConcreteWrapper() + + # Test with empty strings + old_empty, new_empty = wrapper.wrap_upd("", "not_empty") + assert old_empty == "old:" + assert new_empty == "new:not_empty" + + # Test with boolean values + old_bool, new_bool = wrapper.wrap_upd(True, False) + assert old_bool == "old:True" + assert new_bool == "new:False" + + # Test with zero values + old_zero, new_zero = wrapper.wrap_upd(0, 1) + assert old_zero == "old:0" + assert new_zero == "new:1" diff --git a/tests/test_coverup_66.py b/tests/test_coverup_66.py new file mode 100644 index 0000000..7616902 --- /dev/null +++ b/tests/test_coverup_66.py @@ -0,0 +1,53 @@ +# file: objwatch/core.py:94-103 +# asked: {"lines": [94, 103], "branches": []} +# gained: {"lines": [94, 103], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.core import ObjWatch + + +class TestObjWatchExit: + """Test cases for ObjWatch.__exit__ method.""" + + def test_exit_calls_stop(self): + """Test that __exit__ method calls stop() method.""" + # Create a mock ObjWatch instance + objwatch = ObjWatch(targets=['some_module']) + + # Mock the stop method to track if it's called + with patch.object(objwatch, 'stop') as mock_stop: + # Call __exit__ with various parameters + objwatch.__exit__(None, None, None) + + # Verify that stop was called exactly once + mock_stop.assert_called_once() + + def test_exit_with_exception(self): + """Test that __exit__ method calls stop() even when exceptions are present.""" + # Create a mock ObjWatch instance + objwatch = ObjWatch(targets=['some_module']) + + # Mock the stop method to track if it's called + with patch.object(objwatch, 'stop') as mock_stop: + # Call __exit__ with exception parameters + objwatch.__exit__(ValueError, ValueError("test error"), "traceback") + + # Verify that stop was called exactly once + mock_stop.assert_called_once() + + def test_exit_cleanup(self): + """Test that __exit__ properly cleans up resources.""" + # Create a real ObjWatch instance + objwatch = ObjWatch(targets=['some_module']) + + # Mock the tracer to avoid actual tracing + with patch.object(objwatch.tracer, 'stop'): + # Enter context first to set up properly + objwatch.__enter__() + + # Then exit context + objwatch.__exit__(None, None, None) + + # Verify that tracer.stop was called + objwatch.tracer.stop.assert_called_once() diff --git a/tests/test_coverup_67.py b/tests/test_coverup_67.py new file mode 100644 index 0000000..6af5f41 --- /dev/null +++ b/tests/test_coverup_67.py @@ -0,0 +1,148 @@ +# file: objwatch/event_handls.py:21-48 +# asked: {"lines": [21, 28, 29, 30, 31, 32, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 47, 48], "branches": [[29, 0], [29, 30], [47, 0], [47, 48]]} +# gained: {"lines": [21, 28, 29, 30, 31, 32, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 47, 48], "branches": [[29, 0], [29, 30], [47, 0], [47, 48]]} + +import pytest +import signal +import tempfile +import os +import xml.etree.ElementTree as ET +from unittest.mock import patch, MagicMock +from objwatch.event_handls import EventHandls + + +class TestEventHandls: + """Test cases for EventHandls class initialization and XML output functionality.""" + + def test_init_without_xml_output(self): + """Test EventHandls initialization without XML output.""" + handler = EventHandls(output_xml=None) + assert handler.output_xml is None + # Verify no XML-related attributes are set + assert not hasattr(handler, 'is_xml_saved') + assert not hasattr(handler, 'stack_root') + assert not hasattr(handler, 'current_node') + + def test_init_with_xml_output(self): + """Test EventHandls initialization with XML output.""" + with tempfile.NamedTemporaryFile(suffix='.xml', delete=False) as tmp_file: + xml_path = tmp_file.name + + try: + handler = EventHandls(output_xml=xml_path) + assert handler.output_xml == xml_path + assert handler.is_xml_saved is False + assert isinstance(handler.stack_root, ET.Element) + assert handler.stack_root.tag == 'ObjWatch' + assert isinstance(handler.current_node, list) + assert len(handler.current_node) == 1 + assert handler.current_node[0] is handler.stack_root + finally: + if os.path.exists(xml_path): + os.unlink(xml_path) + + def test_signal_handlers_registered(self): + """Test that signal handlers are registered when XML output is enabled.""" + with tempfile.NamedTemporaryFile(suffix='.xml', delete=False) as tmp_file: + xml_path = tmp_file.name + + try: + with patch('signal.signal') as mock_signal: + handler = EventHandls(output_xml=xml_path) + + # Verify signal.signal was called for each signal type + expected_signals = [ + signal.SIGTERM, + signal.SIGINT, + signal.SIGABRT, + signal.SIGHUP, + signal.SIGQUIT, + signal.SIGUSR1, + signal.SIGUSR2, + signal.SIGALRM, + signal.SIGSEGV, + ] + + # Check that signal.signal was called the correct number of times + assert mock_signal.call_count == len(expected_signals) + + # Verify each call was made with the correct signal and handler + for call in mock_signal.call_args_list: + args = call[0] + assert len(args) == 2 + assert args[0] in expected_signals + assert args[1] == handler.signal_handler + + finally: + if os.path.exists(xml_path): + os.unlink(xml_path) + + def test_save_xml_not_called_when_already_saved(self): + """Test that save_xml doesn't save when is_xml_saved is True.""" + with tempfile.NamedTemporaryFile(suffix='.xml', delete=False) as tmp_file: + xml_path = tmp_file.name + + try: + handler = EventHandls(output_xml=xml_path) + handler.is_xml_saved = True + + with patch.object(ET, 'ElementTree') as mock_tree: + with patch('objwatch.utils.logger.log_info') as mock_log_info: + handler.save_xml() + + # Verify no XML operations were performed + mock_tree.assert_not_called() + # Verify no log messages about saving + for call in mock_log_info.call_args_list: + args = call[0] + if len(args) > 0: + assert 'save' not in args[0].lower() + + finally: + if os.path.exists(xml_path): + os.unlink(xml_path) + + def test_save_xml_with_output_disabled(self): + """Test that save_xml does nothing when output_xml is None.""" + handler = EventHandls(output_xml=None) + + # Add XML attributes manually to test the condition + handler.output_xml = None + handler.is_xml_saved = False + + with patch.object(ET, 'ElementTree') as mock_tree: + with patch('objwatch.utils.logger.log_info') as mock_log_info: + handler.save_xml() + + # Verify no XML operations were performed + mock_tree.assert_not_called() + # Verify no log messages about saving + for call in mock_log_info.call_args_list: + args = call[0] + if len(args) > 0: + assert 'save' not in args[0].lower() + + def test_signal_handler_skips_when_xml_already_saved(self): + """Test that signal_handler does nothing when XML is already saved.""" + with tempfile.NamedTemporaryFile(suffix='.xml', delete=False) as tmp_file: + xml_path = tmp_file.name + + try: + handler = EventHandls(output_xml=xml_path) + handler.is_xml_saved = True + + with patch.object(handler, 'save_xml') as mock_save_xml: + with patch('sys.exit') as mock_exit: + with patch('objwatch.event_handls.log_error') as mock_log_error: + handler.signal_handler(signal.SIGINT, None) + + # Verify save_xml was NOT called + mock_save_xml.assert_not_called() + # Verify exit was NOT called + mock_exit.assert_not_called() + # Verify no error was logged + mock_log_error.assert_not_called() + + finally: + if os.path.exists(xml_path): + os.unlink(xml_path) diff --git a/tests/test_coverup_68.py b/tests/test_coverup_68.py new file mode 100644 index 0000000..c9982c3 --- /dev/null +++ b/tests/test_coverup_68.py @@ -0,0 +1,52 @@ +# file: objwatch/utils/weak.py:104-106 +# asked: {"lines": [104, 105, 106], "branches": []} +# gained: {"lines": [104, 105, 106], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryDelItem: + + def test_delitem_sets_dirty_len_and_deletes_key(self): + # Create a dictionary with test data using objects that can be weakly referenced + class TestObject: + def __init__(self, name): + self.name = name + + obj1 = TestObject("obj1") + obj2 = TestObject("obj2") + weak_dict = WeakIdKeyDictionary() + weak_dict[obj1] = "value1" + weak_dict[obj2] = "value2" + + # Verify initial state + assert obj1 in weak_dict + assert obj2 in weak_dict + assert not weak_dict._dirty_len + + # Delete an item + del weak_dict[obj1] + + # Verify postconditions + assert obj1 not in weak_dict + assert obj2 in weak_dict + assert weak_dict._dirty_len + + def test_delitem_with_nonexistent_key_raises_keyerror(self): + # Create a dictionary and test object + class TestObject: + pass + + weak_dict = WeakIdKeyDictionary() + obj = TestObject() + + # Verify the key doesn't exist + assert obj not in weak_dict + + # Attempt to delete non-existent key should raise KeyError + with pytest.raises(KeyError): + del weak_dict[obj] + + # Verify _dirty_len was still set + assert weak_dict._dirty_len diff --git a/tests/test_coverup_69.py b/tests/test_coverup_69.py new file mode 100644 index 0000000..692ada7 --- /dev/null +++ b/tests/test_coverup_69.py @@ -0,0 +1,99 @@ +# file: objwatch/core.py:84-92 +# asked: {"lines": [84, 91, 92], "branches": []} +# gained: {"lines": [84, 91, 92], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.core import ObjWatch + + +class TestObjWatchContextManager: + """Test the context manager functionality of ObjWatch.""" + + def test_enter_method_starts_tracing_and_returns_self(self, monkeypatch): + """Test that __enter__ method calls start() and returns self.""" + # Create a mock tracer + mock_tracer = Mock() + + # Create ObjWatch instance with minimal required parameters + obj_watch = ObjWatch(targets=['some_module']) + + # Mock the tracer to avoid actual tracing + monkeypatch.setattr(obj_watch, 'tracer', mock_tracer) + + # Mock the start method to track if it's called + start_called = False + + def mock_start(): + nonlocal start_called + start_called = True + + monkeypatch.setattr(obj_watch, 'start', mock_start) + + # Call __enter__ method + result = obj_watch.__enter__() + + # Verify that start() was called + assert start_called, "start() method should be called in __enter__" + + # Verify that the method returns self + assert result is obj_watch, "__enter__ should return self" + + def test_enter_method_integration_with_real_start(self, monkeypatch): + """Test __enter__ method integration with actual start method.""" + # Create a mock tracer + mock_tracer = Mock() + + # Create ObjWatch instance + obj_watch = ObjWatch(targets=['some_module']) + + # Mock the tracer to avoid actual tracing + monkeypatch.setattr(obj_watch, 'tracer', mock_tracer) + + # Track if tracer.start() is called + tracer_start_called = False + + def mock_tracer_start(): + nonlocal tracer_start_called + tracer_start_called = True + + mock_tracer.start = mock_tracer_start + + # Call __enter__ method + result = obj_watch.__enter__() + + # Verify that tracer.start() was called through the start() method + assert tracer_start_called, "tracer.start() should be called via start() method" + + # Verify that the method returns self + assert result is obj_watch, "__enter__ should return self" + + def test_context_manager_usage_pattern(self, monkeypatch): + """Test the typical context manager usage pattern.""" + # Create a mock tracer + mock_tracer = Mock() + + # Track calls + start_called = False + + def mock_start(): + nonlocal start_called + start_called = True + + # Create ObjWatch instance first + obj_watch = ObjWatch(targets=['some_module']) + + # Mock the tracer and start method BEFORE entering context + monkeypatch.setattr(obj_watch, 'tracer', mock_tracer) + monkeypatch.setattr(obj_watch, 'start', mock_start) + + # Use the context manager + with obj_watch as context_obj: + # Verify start was called when entering context + assert start_called, "start() should be called when entering context" + + # Verify we have the ObjWatch instance + assert context_obj is obj_watch, "Context should return the same instance" + + # Verify it's an ObjWatch instance + assert isinstance(context_obj, ObjWatch) diff --git a/tests/test_coverup_7.py b/tests/test_coverup_7.py new file mode 100644 index 0000000..2012fbc --- /dev/null +++ b/tests/test_coverup_7.py @@ -0,0 +1,53 @@ +# file: objwatch/event_handls.py:237-253 +# asked: {"lines": [237, 248, 249, 250, 251, 252, 253], "branches": [[249, 250], [249, 251], [251, 252], [251, 253]]} +# gained: {"lines": [237, 248, 249, 250, 251, 252, 253], "branches": [[249, 250], [249, 251], [251, 252], [251, 253]]} + +import pytest +from objwatch.event_handls import EventHandls +from objwatch.events import EventType + + +class TestEventHandlsDetermineChangeType: + """Test cases for EventHandls.determine_change_type method.""" + + def test_determine_change_type_apd(self): + """Test APD event type when current length is greater than old length.""" + handler = EventHandls() + result = handler.determine_change_type(3, 5) + assert result == EventType.APD + + def test_determine_change_type_pop(self): + """Test POP event type when current length is less than old length.""" + handler = EventHandls() + result = handler.determine_change_type(5, 3) + assert result == EventType.POP + + def test_determine_change_type_no_change(self): + """Test None return when current length equals old length.""" + handler = EventHandls() + result = handler.determine_change_type(4, 4) + assert result is None + + def test_determine_change_type_zero_to_positive(self): + """Test APD event type when moving from zero to positive length.""" + handler = EventHandls() + result = handler.determine_change_type(0, 3) + assert result == EventType.APD + + def test_determine_change_type_positive_to_zero(self): + """Test POP event type when moving from positive to zero length.""" + handler = EventHandls() + result = handler.determine_change_type(3, 0) + assert result == EventType.POP + + def test_determine_change_type_negative_diff(self): + """Test POP event type with negative difference.""" + handler = EventHandls() + result = handler.determine_change_type(10, 2) + assert result == EventType.POP + + def test_determine_change_type_positive_diff(self): + """Test APD event type with positive difference.""" + handler = EventHandls() + result = handler.determine_change_type(2, 10) + assert result == EventType.APD diff --git a/tests/test_coverup_70.py b/tests/test_coverup_70.py new file mode 100644 index 0000000..22b144e --- /dev/null +++ b/tests/test_coverup_70.py @@ -0,0 +1,39 @@ +# file: objwatch/targets.py:517-524 +# asked: {"lines": [517, 524], "branches": []} +# gained: {"lines": [517, 524], "branches": []} + +import pytest +from objwatch.targets import Targets + + +class TestTargetsGetFilenameTargets: + """Test cases for Targets.get_filename_targets method.""" + + def test_get_filename_targets_empty(self): + """Test get_filename_targets returns empty set when no filename targets exist.""" + # Arrange + targets = Targets([]) + + # Act + result = targets.get_filename_targets() + + # Assert + assert result == set() + assert isinstance(result, set) + + def test_get_filename_targets_with_files(self, monkeypatch): + """Test get_filename_targets returns populated set when filename targets exist.""" + # Arrange + targets = Targets([]) + expected_files = {'/path/to/file1.py', '/path/to/file2.py'} + + # Use monkeypatch to set the filename_targets attribute + monkeypatch.setattr(targets, 'filename_targets', expected_files.copy()) + + # Act + result = targets.get_filename_targets() + + # Assert + assert result == expected_files + assert isinstance(result, set) + assert len(result) == 2 diff --git a/tests/test_coverup_71.py b/tests/test_coverup_71.py new file mode 100644 index 0000000..1edc734 --- /dev/null +++ b/tests/test_coverup_71.py @@ -0,0 +1,77 @@ +# file: objwatch/wrappers/abc_wrapper.py:22-34 +# asked: {"lines": [22, 23, 34], "branches": []} +# gained: {"lines": [22, 23], "branches": []} + +import pytest +from types import FrameType +from typing import Any, Tuple +from unittest.mock import Mock +from objwatch.wrappers.abc_wrapper import ABCWrapper + + +class TestABCWrapper(ABCWrapper): + """Concrete implementation for testing the abstract base class.""" + + def wrap_call(self, func_name: str, frame: FrameType) -> str: + """Test implementation of wrap_call.""" + return f"Call: {func_name}" + + def wrap_return(self, func_name: str, result: Any) -> str: + """Test implementation of wrap_return.""" + return f"Return: {func_name} -> {result}" + + def wrap_upd(self, old_value: Any, current_value: Any) -> Tuple[str, str]: + """Test implementation of wrap_upd.""" + return f"Old: {old_value}", f"New: {current_value}" + + +def test_abc_wrapper_abstract_methods(): + """Test that ABCWrapper cannot be instantiated directly.""" + with pytest.raises(TypeError): + ABCWrapper() + + +def test_concrete_wrapper_wrap_call(): + """Test the wrap_call method implementation.""" + wrapper = TestABCWrapper() + mock_frame = Mock(spec=FrameType) + + result = wrapper.wrap_call("test_function", mock_frame) + + assert result == "Call: test_function" + assert isinstance(result, str) + + +def test_wrap_call_with_different_function_names(): + """Test wrap_call with various function names.""" + wrapper = TestABCWrapper() + mock_frame = Mock(spec=FrameType) + + # Test with simple function name + result1 = wrapper.wrap_call("func1", mock_frame) + assert result1 == "Call: func1" + + # Test with method name + result2 = wrapper.wrap_call("ClassName.method", mock_frame) + assert result2 == "Call: ClassName.method" + + # Test with special characters + result3 = wrapper.wrap_call("__init__", mock_frame) + assert result3 == "Call: __init__" + + +def test_wrap_call_frame_parameter(): + """Test that wrap_call properly receives and uses the frame parameter.""" + wrapper = TestABCWrapper() + + # Create a more realistic mock frame + mock_frame = Mock(spec=FrameType) + mock_frame.f_code.co_name = "test_function" + mock_frame.f_lineno = 42 + + result = wrapper.wrap_call("test_function", mock_frame) + + # Verify the method was called with the correct parameters + assert result == "Call: test_function" + # The frame parameter is passed but not used in this test implementation + # This test ensures the method signature is correct and the frame is accepted diff --git a/tests/test_coverup_72.py b/tests/test_coverup_72.py new file mode 100644 index 0000000..2ae5a58 --- /dev/null +++ b/tests/test_coverup_72.py @@ -0,0 +1,32 @@ +# file: objwatch/wrappers/cpu_memory_wrapper.py:74-84 +# asked: {"lines": [74, 84], "branches": []} +# gained: {"lines": [74, 84], "branches": []} + +import pytest +from objwatch.wrappers.cpu_memory_wrapper import CPUMemoryWrapper + + +class TestCPUMemoryWrapper: + """Test cases for CPUMemoryWrapper._format_memory method.""" + + def test_format_memory_empty_dict(self): + """Test _format_memory with empty dictionary.""" + wrapper = CPUMemoryWrapper() + result = wrapper._format_memory({}) + assert result == "" + + def test_format_memory_single_item(self): + """Test _format_memory with single key-value pair.""" + wrapper = CPUMemoryWrapper() + stats = {"total": 8589934592} + result = wrapper._format_memory(stats) + assert result == "total: 8589934592" + + def test_format_memory_multiple_items(self): + """Test _format_memory with multiple key-value pairs.""" + wrapper = CPUMemoryWrapper() + stats = {"total": 8589934592, "available": 4294967296, "percent": 50.0} + result = wrapper._format_memory(stats) + expected_parts = ["total: 8589934592", "available: 4294967296", "percent: 50.0"] + assert all(part in result for part in expected_parts) + assert result.count(" | ") == 2 diff --git a/tests/test_coverup_73.py b/tests/test_coverup_73.py new file mode 100644 index 0000000..33507ee --- /dev/null +++ b/tests/test_coverup_73.py @@ -0,0 +1,46 @@ +# file: objwatch/wrappers/cpu_memory_wrapper.py:55-62 +# asked: {"lines": [55, 62], "branches": []} +# gained: {"lines": [55, 62], "branches": []} + +import pytest +from objwatch.wrappers.cpu_memory_wrapper import CPUMemoryWrapper + + +class TestCPUMemoryWrapper: + """Test cases for CPUMemoryWrapper class.""" + + def test_init_with_custom_class_mem_types(self): + """Test that CPUMemoryWrapper uses class-level mem_types attribute.""" + # Save original class mem_types + original_mem_types = CPUMemoryWrapper.mem_types.copy() + + try: + # Set custom mem_types at class level + CPUMemoryWrapper.mem_types = ['total', 'used', 'free'] + + # Create wrapper instance + wrapper = CPUMemoryWrapper() + + # Verify instance uses class-level mem_types + assert wrapper.mem_types == {'total', 'used', 'free'} + finally: + # Restore original class mem_types + CPUMemoryWrapper.mem_types = original_mem_types + + def test_init_with_empty_class_mem_types(self): + """Test that CPUMemoryWrapper handles empty class mem_types.""" + # Save original class mem_types + original_mem_types = CPUMemoryWrapper.mem_types.copy() + + try: + # Set empty mem_types at class level + CPUMemoryWrapper.mem_types = [] + + # Create wrapper instance + wrapper = CPUMemoryWrapper() + + # Verify instance uses empty set + assert wrapper.mem_types == set() + finally: + # Restore original class mem_types + CPUMemoryWrapper.mem_types = original_mem_types diff --git a/tests/test_coverup_74.py b/tests/test_coverup_74.py new file mode 100644 index 0000000..b7d5533 --- /dev/null +++ b/tests/test_coverup_74.py @@ -0,0 +1,50 @@ +# file: objwatch/mp_handls.py:63-71 +# asked: {"lines": [63, 71], "branches": []} +# gained: {"lines": [63, 71], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.mp_handls import MPHandls + + +class TestMPHandlsGetIndex: + """Test cases for MPHandls.get_index method.""" + + def test_get_index_initialized_with_index(self): + """Test get_index returns the correct index when initialized with a valid index.""" + # Setup + handler = MPHandls(framework=None) + handler.initialized = True + handler.index = 5 + + # Execute + result = handler.get_index() + + # Assert + assert result == 5 + + def test_get_index_initialized_with_none_index(self): + """Test get_index returns None when initialized but index is None.""" + # Setup + handler = MPHandls(framework=None) + handler.initialized = True + handler.index = None + + # Execute + result = handler.get_index() + + # Assert + assert result is None + + def test_get_index_not_initialized(self): + """Test get_index returns the index value regardless of initialization status.""" + # Setup + handler = MPHandls(framework=None) + handler.initialized = False + handler.index = 42 + + # Execute + result = handler.get_index() + + # Assert + assert result == 42 diff --git a/tests/test_coverup_76.py b/tests/test_coverup_76.py new file mode 100644 index 0000000..017dca0 --- /dev/null +++ b/tests/test_coverup_76.py @@ -0,0 +1,242 @@ +# file: objwatch/tracer.py:654-689 +# asked: {"lines": [654, 663, 664, 666, 667, 669, 670, 671, 672, 674, 675, 676, 678, 679, 680, 681, 683, 684, 687, 688, 689], "branches": [[666, 667], [666, 669], [669, 670], [669, 671], [671, 672], [671, 674], [674, 0], [674, 675], [675, 676], [675, 678], [688, 674], [688, 689]]} +# gained: {"lines": [654, 663, 664, 666, 667, 669, 670, 671, 672, 674, 675, 676, 678, 679, 680, 681, 683, 684, 687, 688, 689], "branches": [[666, 667], [666, 669], [669, 670], [669, 671], [671, 672], [671, 674], [674, 0], [674, 675], [675, 676], [675, 678], [688, 674], [688, 689]]} + +import pytest +from types import FrameType +from unittest.mock import Mock, MagicMock, patch +import sys +from objwatch.config import ObjWatchConfig + + +class TestTracerTrackGlobalsChange: + """Test cases for Tracer._track_globals_change method to achieve full coverage.""" + + def test_track_globals_change_with_globals_disabled(self, monkeypatch): + """Test that method returns early when with_globals is False.""" + from objwatch.tracer import Tracer + + # Create a minimal config with with_globals=False + config = Mock(spec=ObjWatchConfig) + config.with_globals = False + config.with_locals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.wrapper = None + config.indexes = None + + tracer = Tracer(config) + + frame = Mock(spec=FrameType) + frame.f_globals = {'__name__': 'test_module'} + + # Should return early without processing + tracer._track_globals_change(frame, 123) + + # Verify no changes were made to tracked collections + # Since with_globals=False, these attributes shouldn't exist + assert not hasattr(tracer, 'tracked_globals') + assert not hasattr(tracer, 'tracked_globals_lens') + + def test_track_globals_change_new_module_initialization(self, monkeypatch): + """Test initialization of new module in tracked collections.""" + from objwatch.tracer import Tracer + + # Create config with with_globals=True + config = Mock(spec=ObjWatchConfig) + config.with_globals = True + config.with_locals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.wrapper = None + config.indexes = None + + tracer = Tracer(config) + + # Mock _should_trace_global to return True for specific globals + def mock_should_trace_global(module, global_name): + return global_name in ['tracked_var', 'seq_var'] + + tracer._should_trace_global = mock_should_trace_global + + frame = Mock(spec=FrameType) + frame.f_globals = { + '__name__': 'new_module', + 'tracked_var': 42, + 'seq_var': [1, 2, 3], + 'ignored_var': 'should_not_track', + } + + tracer._track_globals_change(frame, 456) + + # Verify module was initialized in both collections + assert 'new_module' in tracer.tracked_globals + assert 'new_module' in tracer.tracked_globals_lens + # Verify tracked variables were stored + assert tracer.tracked_globals['new_module']['tracked_var'] == 42 + assert tracer.tracked_globals['new_module']['seq_var'] == [1, 2, 3] + # Verify sequence length was tracked + assert tracer.tracked_globals_lens['new_module']['seq_var'] == 3 + # Verify ignored variable was not tracked + assert 'ignored_var' not in tracer.tracked_globals['new_module'] + + def test_track_globals_change_sequence_types_tracking(self, monkeypatch): + """Test tracking of sequence types (list, set, dict, tuple) with length.""" + from objwatch.tracer import Tracer + from objwatch.constants import Constants + + # Create config with with_globals=True + config = Mock(spec=ObjWatchConfig) + config.with_globals = True + config.with_locals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.wrapper = None + config.indexes = None + + tracer = Tracer(config) + tracer.tracked_globals = {'existing_module': {}} + tracer.tracked_globals_lens = {'existing_module': {}} + + # Mock _should_trace_global to return True for all + tracer._should_trace_global = Mock(return_value=True) + + # Mock _handle_change_type to verify it's called correctly + mock_handle_change = Mock() + tracer._handle_change_type = mock_handle_change + + frame = Mock(spec=FrameType) + frame.f_globals = { + '__name__': 'existing_module', + 'list_var': [1, 2], + 'set_var': {1, 2, 3}, + 'dict_var': {'a': 1, 'b': 2}, + 'tuple_var': (4, 5, 6, 7), + 'int_var': 100, + } + + tracer._track_globals_change(frame, 789) + + # Verify all variables were tracked + assert tracer.tracked_globals['existing_module']['list_var'] == [1, 2] + assert tracer.tracked_globals['existing_module']['set_var'] == {1, 2, 3} + assert tracer.tracked_globals['existing_module']['dict_var'] == {'a': 1, 'b': 2} + assert tracer.tracked_globals['existing_module']['tuple_var'] == (4, 5, 6, 7) + assert tracer.tracked_globals['existing_module']['int_var'] == 100 + + # Verify sequence lengths were tracked for sequence types + assert tracer.tracked_globals_lens['existing_module']['list_var'] == 2 + assert tracer.tracked_globals_lens['existing_module']['set_var'] == 3 + assert tracer.tracked_globals_lens['existing_module']['dict_var'] == 2 + assert tracer.tracked_globals_lens['existing_module']['tuple_var'] == 4 + # Non-sequence types should not have length tracked + assert 'int_var' not in tracer.tracked_globals_lens['existing_module'] + + # Verify _handle_change_type was called for each variable + # There are 5 variables, but __name__ is also processed, making 6 calls + assert mock_handle_change.call_count == 6 + + def test_track_globals_change_with_existing_values(self, monkeypatch): + """Test tracking when there are existing values in tracked collections.""" + from objwatch.tracer import Tracer + + # Create config with with_globals=True + config = Mock(spec=ObjWatchConfig) + config.with_globals = True + config.with_locals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.wrapper = None + config.indexes = None + + tracer = Tracer(config) + tracer.tracked_globals = {'test_module': {'existing_var': 'old_value', 'seq_var': [1, 2]}} + tracer.tracked_globals_lens = {'test_module': {'seq_var': 2}} + + # Mock _should_trace_global to return True for all + tracer._should_trace_global = Mock(return_value=True) + + # Mock _handle_change_type to capture calls + mock_handle_change = Mock() + tracer._handle_change_type = mock_handle_change + + frame = Mock(spec=FrameType) + frame.f_globals = { + '__name__': 'test_module', + 'existing_var': 'new_value', + 'seq_var': [1, 2, 3, 4], # Length changed from 2 to 4 + 'new_var': 'fresh_value', + } + + tracer._track_globals_change(frame, 999) + + # Verify values were updated + assert tracer.tracked_globals['test_module']['existing_var'] == 'new_value' + assert tracer.tracked_globals['test_module']['seq_var'] == [1, 2, 3, 4] + assert tracer.tracked_globals['test_module']['new_var'] == 'fresh_value' + + # Verify sequence length was updated + assert tracer.tracked_globals_lens['test_module']['seq_var'] == 4 + + # Verify _handle_change_type was called for each variable with correct parameters + # 3 variables + __name__ = 4 calls + assert mock_handle_change.call_count == 4 + + def test_track_globals_change_filtering_by_should_trace(self, monkeypatch): + """Test that _should_trace_global filtering works correctly.""" + from objwatch.tracer import Tracer + + # Create config with with_globals=True + config = Mock(spec=ObjWatchConfig) + config.with_globals = True + config.with_locals = False + config.targets = [] + config.exclude_targets = [] + config.output_xml = False + config.framework = None + config.wrapper = None + config.indexes = None + + tracer = Tracer(config) + + # Create a mock that returns True only for specific variables + should_trace_calls = [] + + def mock_should_trace_global(module, global_name): + should_trace_calls.append((module, global_name)) + return global_name in ['tracked1', 'tracked2'] + + tracer._should_trace_global = mock_should_trace_global + + frame = Mock(spec=FrameType) + frame.f_globals = { + '__name__': 'filter_module', + 'tracked1': 'value1', + 'tracked2': [1, 2], + 'ignored1': 'skip1', + 'ignored2': {'a': 1}, + } + + tracer._track_globals_change(frame, 111) + + # Verify only tracked variables were processed + assert set(tracer.tracked_globals['filter_module'].keys()) == {'tracked1', 'tracked2'} + assert set(tracer.tracked_globals_lens['filter_module'].keys()) == {'tracked2'} # Only sequence type + + # Verify _should_trace_global was called for each global including __name__ + expected_calls = [ + ('filter_module', '__name__'), + ('filter_module', 'tracked1'), + ('filter_module', 'tracked2'), + ('filter_module', 'ignored1'), + ('filter_module', 'ignored2'), + ] + assert set(should_trace_calls) == set(expected_calls) diff --git a/tests/test_coverup_77.py b/tests/test_coverup_77.py new file mode 100644 index 0000000..628b0cc --- /dev/null +++ b/tests/test_coverup_77.py @@ -0,0 +1,25 @@ +# file: objwatch/wrappers/abc_wrapper.py:17-20 +# asked: {"lines": [17, 20], "branches": []} +# gained: {"lines": [17, 20], "branches": []} + +import pytest +from objwatch.wrappers.abc_wrapper import ABCWrapper + + +class TestABCBWrapper: + def test_init_sets_format_sequence_func_to_none(self): + """Test that ABCWrapper.__init__ sets format_sequence_func to None.""" + + # Create a concrete subclass to test the abstract base class + class ConcreteWrapper(ABCWrapper): + def wrap_call(self, func_name, frame): + return "" + + def wrap_return(self, func_name, result): + return "" + + def wrap_upd(self, old_value, current_value): + return "", "" + + wrapper = ConcreteWrapper() + assert wrapper.format_sequence_func is None diff --git a/tests/test_coverup_79.py b/tests/test_coverup_79.py new file mode 100644 index 0000000..2d13dbf --- /dev/null +++ b/tests/test_coverup_79.py @@ -0,0 +1,16 @@ +# file: objwatch/utils/weak.py:118-119 +# asked: {"lines": [118, 119], "branches": []} +# gained: {"lines": [118, 119], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary + + +class TestWeakIdKeyDictionary: + def test_repr(self): + """Test that __repr__ method returns expected format""" + weak_dict = WeakIdKeyDictionary() + repr_str = repr(weak_dict) + assert repr_str.startswith("") + assert "WeakIdKeyDictionary" in repr_str diff --git a/tests/test_coverup_8.py b/tests/test_coverup_8.py new file mode 100644 index 0000000..b6e8d4a --- /dev/null +++ b/tests/test_coverup_8.py @@ -0,0 +1,69 @@ +# file: objwatch/targets.py:48-65 +# asked: {"lines": [48, 58, 59, 60, 61, 62, 64, 65], "branches": [[58, 59], [58, 65], [59, 60], [59, 61], [61, 62], [61, 64]]} +# gained: {"lines": [48, 58, 59, 60, 61, 62, 64, 65], "branches": [[58, 59], [58, 65], [59, 60], [59, 61], [61, 62], [61, 64]]} + +import pytest +from objwatch.targets import deep_merge + + +def test_deep_merge_dict_recursion(): + """Test deep_merge with nested dictionaries to cover recursive dict merging.""" + source = {"a": {"b": 1, "c": 2}} + update = {"a": {"b": 3, "d": 4}} + result = deep_merge(source, update) + expected = {"a": {"b": 3, "c": 2, "d": 4}} + assert result == expected + assert result is source # Should return reference to source + + +def test_deep_merge_list_union(): + """Test deep_merge with lists to cover list union functionality.""" + source = {"items": [1, 2, 3]} + update = {"items": [3, 4, 5]} + result = deep_merge(source, update) + expected_items = sorted([1, 2, 3, 4, 5]) # set union removes duplicates + assert sorted(result["items"]) == expected_items + assert result is source + + +def test_deep_merge_scalar_overwrite(): + """Test deep_merge with scalar values to cover the else branch.""" + source = {"name": "old", "value": 10} + update = {"name": "new", "value": 20} + result = deep_merge(source, update) + expected = {"name": "new", "value": 20} + assert result == expected + assert result is source + + +def test_deep_merge_mixed_types(): + """Test deep_merge with mixed nested structures.""" + source = {"config": {"debug": True, "items": [1, 2]}, "name": "test"} + update = {"config": {"debug": False, "items": [2, 3], "timeout": 30}, "name": "updated"} + result = deep_merge(source, update) + expected = {"config": {"debug": False, "items": [1, 2, 3], "timeout": 30}, "name": "updated"} + assert result["config"]["debug"] == False + assert sorted(result["config"]["items"]) == [1, 2, 3] + assert result["config"]["timeout"] == 30 + assert result["name"] == "updated" + assert result is source + + +def test_deep_merge_empty_dicts(): + """Test deep_merge with empty dictionaries.""" + source = {} + update = {"a": 1, "b": {"c": 2}} + result = deep_merge(source, update) + expected = {"a": 1, "b": {"c": 2}} + assert result == expected + assert result is source + + +def test_deep_merge_nonexistent_key(): + """Test deep_merge when source doesn't have a key that update has.""" + source = {"existing": "value"} + update = {"new_key": "new_value", "nested": {"a": 1}} + result = deep_merge(source, update) + expected = {"existing": "value", "new_key": "new_value", "nested": {"a": 1}} + assert result == expected + assert result is source diff --git a/tests/test_coverup_81.py b/tests/test_coverup_81.py new file mode 100644 index 0000000..345784f --- /dev/null +++ b/tests/test_coverup_81.py @@ -0,0 +1,73 @@ +# file: objwatch/wrappers/cpu_memory_wrapper.py:86-97 +# asked: {"lines": [86, 97], "branches": []} +# gained: {"lines": [86, 97], "branches": []} + +import pytest +from types import FrameType +from unittest.mock import Mock, patch +from objwatch.wrappers.cpu_memory_wrapper import CPUMemoryWrapper + + +class TestCPUMemoryWrapper: + """Test cases for CPUMemoryWrapper class.""" + + def test_wrap_call_executes_all_lines(self): + """Test that wrap_call method executes all lines including the return statement.""" + # Create a CPUMemoryWrapper instance + wrapper = CPUMemoryWrapper() + + # Mock a frame object + mock_frame = Mock(spec=FrameType) + + # Test with a sample function name + func_name = "test_function" + + # Mock the internal methods to verify they are called + with patch.object(wrapper, '_capture_memory') as mock_capture, patch.object( + wrapper, '_format_memory' + ) as mock_format: + + # Set up the mock return values + mock_capture.return_value = {'total': 8589934592, 'available': 4294967296, 'percent': 50.0} + mock_format.return_value = "total: 8589934592 | available: 4294967296 | percent: 50.0" + + # Call the method under test + result = wrapper.wrap_call(func_name, mock_frame) + + # Verify the internal methods were called correctly + mock_capture.assert_called_once() + mock_format.assert_called_once_with({'total': 8589934592, 'available': 4294967296, 'percent': 50.0}) + + # Verify the result + assert result == "total: 8589934592 | available: 4294967296 | percent: 50.0" + + def test_wrap_call_with_different_mem_types(self): + """Test wrap_call with different memory type configurations.""" + # Create a CPUMemoryWrapper instance with custom memory types + wrapper = CPUMemoryWrapper() + wrapper.mem_types = {'used', 'free'} # Use different memory types + + # Mock a frame object + mock_frame = Mock(spec=FrameType) + + # Test with a sample function name + func_name = "another_function" + + # Mock the internal methods + with patch.object(wrapper, '_capture_memory') as mock_capture, patch.object( + wrapper, '_format_memory' + ) as mock_format: + + # Set up the mock return values for different memory types + mock_capture.return_value = {'used': 4294967296, 'free': 4294967296} + mock_format.return_value = "used: 4294967296 | free: 4294967296" + + # Call the method under test + result = wrapper.wrap_call(func_name, mock_frame) + + # Verify the internal methods were called correctly + mock_capture.assert_called_once() + mock_format.assert_called_once_with({'used': 4294967296, 'free': 4294967296}) + + # Verify the result + assert result == "used: 4294967296 | free: 4294967296" diff --git a/tests/test_coverup_82.py b/tests/test_coverup_82.py new file mode 100644 index 0000000..f46d782 --- /dev/null +++ b/tests/test_coverup_82.py @@ -0,0 +1,253 @@ +# file: objwatch/tracer.py:171-236 +# asked: {"lines": [171, 191, 192, 193, 194, 195, 196, 197, 200, 202, 203, 205, 208, 211, 212, 213, 214, 217, 218, 219, 220, 223, 224, 227, 228, 230, 231, 232, 233, 234, 235], "branches": [[200, 202], [200, 230], [203, 205], [203, 223], [212, 213], [212, 217], [218, 203], [218, 219], [223, 224], [223, 227], [227, 200], [227, 228]]} +# gained: {"lines": [171, 191, 192, 193, 194, 195, 196, 197, 200, 202, 203, 205, 208, 211, 212, 213, 214, 217, 218, 219, 220, 223, 224, 227, 228, 230, 231, 232, 233, 234, 235], "branches": [[200, 202], [200, 230], [203, 205], [203, 223], [212, 213], [212, 217], [218, 203], [218, 219], [223, 224], [223, 227], [227, 200], [227, 228]]} + +import pytest +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerBuildExcludeTargetIndex: + """Test cases for Tracer._build_exclude_target_index method.""" + + def test_build_exclude_target_index_empty(self): + """Test _build_exclude_target_index with empty exclude_targets.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Clear any existing exclude targets + tracer.exclude_targets = {} + + # Call the method + tracer._build_exclude_target_index() + + # Verify all indexes are empty + assert tracer.exclude_module_index == set() + assert tracer.exclude_class_index == {} + assert tracer.exclude_method_index == {} + assert tracer.exclude_attribute_index == {} + assert tracer.exclude_function_index == {} + assert tracer.exclude_global_index == {} + assert tracer.exclude_class_info == {} + assert tracer.exclude_index_map == {'class': {}, 'method': {}, 'attribute': {}, 'function': {}, 'global': {}} + + def test_build_exclude_target_index_with_classes_only(self): + """Test _build_exclude_target_index with only classes in exclude_targets.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Set up exclude targets with classes only + tracer.exclude_targets = { + 'module1': { + 'classes': {'ClassA': {'methods': [], 'attributes': []}, 'ClassB': {'methods': [], 'attributes': []}}, + 'functions': [], + 'globals': [], + } + } + + # Call the method + tracer._build_exclude_target_index() + + # Verify module index + assert tracer.exclude_module_index == {'module1'} + + # Verify class index + assert tracer.exclude_class_index == {'module1': {'ClassA', 'ClassB'}} + + # Verify method index (empty since no methods specified) + assert tracer.exclude_method_index == {} + + # Verify attribute index (empty since no attributes specified) + assert tracer.exclude_attribute_index == {} + + # Verify function index (empty) + assert tracer.exclude_function_index == {} + + # Verify global index (empty) + assert tracer.exclude_global_index == {} + + # Verify class info + assert tracer.exclude_class_info == { + 'module1': {'ClassA': {'methods': [], 'attributes': []}, 'ClassB': {'methods': [], 'attributes': []}} + } + + # Verify index map + assert tracer.exclude_index_map == { + 'class': {'module1': {'ClassA', 'ClassB'}}, + 'method': {}, + 'attribute': {}, + 'function': {}, + 'global': {}, + } + + def test_build_exclude_target_index_with_methods_and_attributes(self): + """Test _build_exclude_target_index with methods and attributes in classes.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Set up exclude targets with classes containing methods and attributes + tracer.exclude_targets = { + 'module1': { + 'classes': { + 'ClassA': {'methods': ['method1', 'method2'], 'attributes': ['attr1', 'attr2']}, + 'ClassB': {'methods': ['method3'], 'attributes': ['attr3']}, + }, + 'functions': [], + 'globals': [], + } + } + + # Call the method + tracer._build_exclude_target_index() + + # Verify module index + assert tracer.exclude_module_index == {'module1'} + + # Verify class index + assert tracer.exclude_class_index == {'module1': {'ClassA', 'ClassB'}} + + # Verify method index + assert tracer.exclude_method_index == {'module1': {'ClassA': {'method1', 'method2'}, 'ClassB': {'method3'}}} + + # Verify attribute index + assert tracer.exclude_attribute_index == {'module1': {'ClassA': {'attr1', 'attr2'}, 'ClassB': {'attr3'}}} + + # Verify function index (empty) + assert tracer.exclude_function_index == {} + + # Verify global index (empty) + assert tracer.exclude_global_index == {} + + # Verify class info + assert tracer.exclude_class_info == { + 'module1': { + 'ClassA': {'methods': ['method1', 'method2'], 'attributes': ['attr1', 'attr2']}, + 'ClassB': {'methods': ['method3'], 'attributes': ['attr3']}, + } + } + + # Verify index map + assert tracer.exclude_index_map == { + 'class': {'module1': {'ClassA', 'ClassB'}}, + 'method': {'module1': {'ClassA': {'method1', 'method2'}, 'ClassB': {'method3'}}}, + 'attribute': {'module1': {'ClassA': {'attr1', 'attr2'}, 'ClassB': {'attr3'}}}, + 'function': {}, + 'global': {}, + } + + def test_build_exclude_target_index_with_functions_and_globals(self): + """Test _build_exclude_target_index with functions and globals.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Set up exclude targets with functions and globals + tracer.exclude_targets = { + 'module1': {'classes': {}, 'functions': ['func1', 'func2'], 'globals': ['global1', 'global2']}, + 'module2': {'classes': {}, 'functions': ['func3'], 'globals': ['global3']}, + } + + # Call the method + tracer._build_exclude_target_index() + + # Verify module index + assert tracer.exclude_module_index == {'module1', 'module2'} + + # Verify class index (empty) + assert tracer.exclude_class_index == {} + + # Verify method index (empty) + assert tracer.exclude_method_index == {} + + # Verify attribute index (empty) + assert tracer.exclude_attribute_index == {} + + # Verify function index + assert tracer.exclude_function_index == {'module1': {'func1', 'func2'}, 'module2': {'func3'}} + + # Verify global index + assert tracer.exclude_global_index == {'module1': {'global1', 'global2'}, 'module2': {'global3'}} + + # Verify class info (empty) + assert tracer.exclude_class_info == {} + + # Verify index map + assert tracer.exclude_index_map == { + 'class': {}, + 'method': {}, + 'attribute': {}, + 'function': {'module1': {'func1', 'func2'}, 'module2': {'func3'}}, + 'global': {'module1': {'global1', 'global2'}, 'module2': {'global3'}}, + } + + def test_build_exclude_target_index_complete_scenario(self): + """Test _build_exclude_target_index with complete scenario including all types.""" + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Set up comprehensive exclude targets + tracer.exclude_targets = { + 'module1': { + 'classes': { + 'ClassA': {'methods': ['method_a1', 'method_a2'], 'attributes': ['attr_a1', 'attr_a2']}, + 'ClassB': {'methods': ['method_b1'], 'attributes': ['attr_b1']}, + }, + 'functions': ['func1', 'func2'], + 'globals': ['global1', 'global2'], + }, + 'module2': { + 'classes': {'ClassC': {'methods': ['method_c1'], 'attributes': ['attr_c1', 'attr_c2']}}, + 'functions': ['func3'], + 'globals': ['global3'], + }, + } + + # Call the method + tracer._build_exclude_target_index() + + # Verify module index + assert tracer.exclude_module_index == {'module1', 'module2'} + + # Verify class index + assert tracer.exclude_class_index == {'module1': {'ClassA', 'ClassB'}, 'module2': {'ClassC'}} + + # Verify method index + assert tracer.exclude_method_index == { + 'module1': {'ClassA': {'method_a1', 'method_a2'}, 'ClassB': {'method_b1'}}, + 'module2': {'ClassC': {'method_c1'}}, + } + + # Verify attribute index + assert tracer.exclude_attribute_index == { + 'module1': {'ClassA': {'attr_a1', 'attr_a2'}, 'ClassB': {'attr_b1'}}, + 'module2': {'ClassC': {'attr_c1', 'attr_c2'}}, + } + + # Verify function index + assert tracer.exclude_function_index == {'module1': {'func1', 'func2'}, 'module2': {'func3'}} + + # Verify global index + assert tracer.exclude_global_index == {'module1': {'global1', 'global2'}, 'module2': {'global3'}} + + # Verify class info + assert tracer.exclude_class_info == { + 'module1': { + 'ClassA': {'methods': ['method_a1', 'method_a2'], 'attributes': ['attr_a1', 'attr_a2']}, + 'ClassB': {'methods': ['method_b1'], 'attributes': ['attr_b1']}, + }, + 'module2': {'ClassC': {'methods': ['method_c1'], 'attributes': ['attr_c1', 'attr_c2']}}, + } + + # Verify index map + assert tracer.exclude_index_map == { + 'class': {'module1': {'ClassA', 'ClassB'}, 'module2': {'ClassC'}}, + 'method': { + 'module1': {'ClassA': {'method_a1', 'method_a2'}, 'ClassB': {'method_b1'}}, + 'module2': {'ClassC': {'method_c1'}}, + }, + 'attribute': { + 'module1': {'ClassA': {'attr_a1', 'attr_a2'}, 'ClassB': {'attr_b1'}}, + 'module2': {'ClassC': {'attr_c1', 'attr_c2'}}, + }, + 'function': {'module1': {'func1', 'func2'}, 'module2': {'func3'}}, + 'global': {'module1': {'global1', 'global2'}, 'module2': {'global3'}}, + } diff --git a/tests/test_coverup_83.py b/tests/test_coverup_83.py new file mode 100644 index 0000000..50439c2 --- /dev/null +++ b/tests/test_coverup_83.py @@ -0,0 +1,280 @@ +# file: objwatch/tracer.py:106-169 +# asked: {"lines": [106, 122, 123, 124, 125, 126, 127, 128, 131, 133, 134, 136, 139, 142, 143, 144, 145, 146, 149, 150, 151, 152, 153, 156, 157, 160, 161, 163, 164, 165, 166, 167, 168], "branches": [[131, 133], [131, 163], [134, 136], [134, 156], [142, 143], [142, 149], [144, 145], [144, 149], [149, 134], [149, 150], [151, 134], [151, 152], [156, 157], [156, 160], [160, 131], [160, 161]]} +# gained: {"lines": [106, 122, 123, 124, 125, 126, 127, 128, 131, 133, 134, 136, 139, 142, 143, 144, 145, 146, 149, 150, 151, 152, 153, 156, 157, 160, 161, 163, 164, 165, 166, 167, 168], "branches": [[131, 133], [131, 163], [134, 136], [134, 156], [142, 143], [142, 149], [144, 145], [144, 149], [149, 134], [149, 150], [151, 134], [151, 152], [156, 157], [156, 160], [160, 131], [160, 161]]} + +import pytest +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerBuildTargetIndex: + """Test cases for Tracer._build_target_index method""" + + def test_build_target_index_empty_targets(self): + """Test _build_target_index with empty targets - should handle gracefully""" + # Create a minimal valid config with empty targets dict + # The tracer should handle this gracefully by creating empty indexes + config = ObjWatchConfig(targets=['dummy_module']) + tracer = Tracer(config) + + # Manually set empty targets to test the _build_target_index behavior + tracer.targets = {} + tracer._build_target_index() + + # Verify all indexes are empty + assert tracer.module_index == set() + assert tracer.class_index == {} + assert tracer.method_index == {} + assert tracer.attribute_index == {} + assert tracer.function_index == {} + assert tracer.global_index == {} + assert tracer.class_info == {} + assert tracer.index_map == {'class': {}, 'method': {}, 'attribute': {}, 'function': {}, 'global': {}} + + def test_build_target_index_with_classes_methods_attributes(self): + """Test _build_target_index with classes, methods, and attributes""" + targets = ['test_module'] + config = ObjWatchConfig(targets=targets) + tracer = Tracer(config) + + # Manually set targets to test specific structure + tracer.targets = { + 'test_module': { + 'classes': { + 'TestClass': { + 'track_all': False, + 'methods': ['method1', 'method2'], + 'attributes': ['attr1', 'attr2'], + } + } + } + } + tracer._build_target_index() + + # Verify module index + assert tracer.module_index == {'test_module'} + + # Verify class index + assert tracer.class_index == {'test_module': {'TestClass'}} + + # Verify method index + assert tracer.method_index == {'test_module': {'TestClass': {'method1', 'method2'}}} + + # Verify attribute index + assert tracer.attribute_index == {'test_module': {'TestClass': {'attr1', 'attr2'}}} + + # Verify class info + assert tracer.class_info == { + 'test_module': { + 'TestClass': {'track_all': False, 'methods': ['method1', 'method2'], 'attributes': ['attr1', 'attr2']} + } + } + + # Verify index map + assert tracer.index_map == { + 'class': {'test_module': {'TestClass'}}, + 'method': {'test_module': {'TestClass': {'method1', 'method2'}}}, + 'attribute': {'test_module': {'TestClass': {'attr1', 'attr2'}}}, + 'function': {}, + 'global': {}, + } + + def test_build_target_index_with_track_all_classes(self): + """Test _build_target_index with classes that have track_all=True""" + targets = ['test_module'] + config = ObjWatchConfig(targets=targets) + tracer = Tracer(config) + + # Manually set targets to test track_all behavior + tracer.targets = { + 'test_module': { + 'classes': { + 'TestClass': { + 'track_all': True, + 'methods': ['method1', 'method2'], + 'attributes': ['attr1', 'attr2'], + } + } + } + } + tracer._build_target_index() + + # Verify module index + assert tracer.module_index == {'test_module'} + + # Verify class index + assert tracer.class_index == {'test_module': {'TestClass'}} + + # Verify method index is empty (because track_all=True) + assert tracer.method_index == {} + + # Verify attribute index is empty (because track_all=True) + assert tracer.attribute_index == {} + + # Verify class info + assert tracer.class_info == { + 'test_module': { + 'TestClass': {'track_all': True, 'methods': ['method1', 'method2'], 'attributes': ['attr1', 'attr2']} + } + } + + # Verify index map + assert tracer.index_map == { + 'class': {'test_module': {'TestClass'}}, + 'method': {}, + 'attribute': {}, + 'function': {}, + 'global': {}, + } + + def test_build_target_index_with_functions(self): + """Test _build_target_index with functions""" + targets = ['test_module'] + config = ObjWatchConfig(targets=targets) + tracer = Tracer(config) + + # Manually set targets to test functions + tracer.targets = {'test_module': {'functions': ['func1', 'func2', 'func3']}} + tracer._build_target_index() + + # Verify module index + assert tracer.module_index == {'test_module'} + + # Verify function index + assert tracer.function_index == {'test_module': {'func1', 'func2', 'func3'}} + + # Verify index map + assert tracer.index_map == { + 'class': {}, + 'method': {}, + 'attribute': {}, + 'function': {'test_module': {'func1', 'func2', 'func3'}}, + 'global': {}, + } + + def test_build_target_index_with_globals(self): + """Test _build_target_index with global variables""" + targets = ['test_module'] + config = ObjWatchConfig(targets=targets) + tracer = Tracer(config) + + # Manually set targets to test globals + tracer.targets = {'test_module': {'globals': ['global_var1', 'global_var2']}} + tracer._build_target_index() + + # Verify module index + assert tracer.module_index == {'test_module'} + + # Verify global index + assert tracer.global_index == {'test_module': {'global_var1', 'global_var2'}} + + # Verify index map + assert tracer.index_map == { + 'class': {}, + 'method': {}, + 'attribute': {}, + 'function': {}, + 'global': {'test_module': {'global_var1', 'global_var2'}}, + } + + def test_build_target_index_complete_scenario(self): + """Test _build_target_index with complete scenario including all components""" + targets = ['module1', 'module2'] + config = ObjWatchConfig(targets=targets) + tracer = Tracer(config) + + # Manually set targets for complete scenario + tracer.targets = { + 'module1': { + 'classes': { + 'ClassA': {'track_all': False, 'methods': ['method_a1', 'method_a2'], 'attributes': ['attr_a1']}, + 'ClassB': {'track_all': True, 'methods': ['method_b1'], 'attributes': ['attr_b1', 'attr_b2']}, + }, + 'functions': ['func1', 'func2'], + 'globals': ['global1'], + }, + 'module2': { + 'classes': {'ClassC': {'track_all': False, 'methods': ['method_c1'], 'attributes': []}}, + 'functions': ['func3'], + 'globals': ['global2', 'global3'], + }, + } + tracer._build_target_index() + + # Verify module index + assert tracer.module_index == {'module1', 'module2'} + + # Verify class index + assert tracer.class_index == {'module1': {'ClassA', 'ClassB'}, 'module2': {'ClassC'}} + + # Verify method index (only for classes with track_all=False) + assert tracer.method_index == { + 'module1': {'ClassA': {'method_a1', 'method_a2'}}, + 'module2': {'ClassC': {'method_c1'}}, + } + + # Verify attribute index (only for classes with track_all=False) + # Note: ClassC has empty attributes list, so it should not appear in attribute_index + assert tracer.attribute_index == {'module1': {'ClassA': {'attr_a1'}}} + + # Verify function index + assert tracer.function_index == {'module1': {'func1', 'func2'}, 'module2': {'func3'}} + + # Verify global index + assert tracer.global_index == {'module1': {'global1'}, 'module2': {'global2', 'global3'}} + + # Verify class info + assert tracer.class_info == { + 'module1': { + 'ClassA': {'track_all': False, 'methods': ['method_a1', 'method_a2'], 'attributes': ['attr_a1']}, + 'ClassB': {'track_all': True, 'methods': ['method_b1'], 'attributes': ['attr_b1', 'attr_b2']}, + }, + 'module2': {'ClassC': {'track_all': False, 'methods': ['method_c1'], 'attributes': []}}, + } + + # Verify index map + assert tracer.index_map == { + 'class': {'module1': {'ClassA', 'ClassB'}, 'module2': {'ClassC'}}, + 'method': {'module1': {'ClassA': {'method_a1', 'method_a2'}}, 'module2': {'ClassC': {'method_c1'}}}, + 'attribute': {'module1': {'ClassA': {'attr_a1'}}}, + 'function': {'module1': {'func1', 'func2'}, 'module2': {'func3'}}, + 'global': {'module1': {'global1'}, 'module2': {'global2', 'global3'}}, + } + + def test_build_target_index_with_empty_methods_attributes(self): + """Test _build_target_index with classes that have empty methods and attributes lists""" + targets = ['test_module'] + config = ObjWatchConfig(targets=targets) + tracer = Tracer(config) + + # Manually set targets to test empty lists + tracer.targets = { + 'test_module': {'classes': {'TestClass': {'track_all': False, 'methods': [], 'attributes': []}}} + } + tracer._build_target_index() + + # Verify module index + assert tracer.module_index == {'test_module'} + + # Verify class index + assert tracer.class_index == {'test_module': {'TestClass'}} + + # Verify method index is empty (because methods list is empty) + assert tracer.method_index == {} + + # Verify attribute index is empty (because attributes list is empty) + assert tracer.attribute_index == {} + + # Verify class info + assert tracer.class_info == { + 'test_module': {'TestClass': {'track_all': False, 'methods': [], 'attributes': []}} + } + + # Verify index map + assert tracer.index_map == { + 'class': {'test_module': {'TestClass'}}, + 'method': {}, + 'attribute': {}, + 'function': {}, + 'global': {}, + } diff --git a/tests/test_coverup_85.py b/tests/test_coverup_85.py new file mode 100644 index 0000000..b59f3ab --- /dev/null +++ b/tests/test_coverup_85.py @@ -0,0 +1,192 @@ +# file: objwatch/tracer.py:304-323 +# asked: {"lines": [304, 305, 317, 318, 320, 321, 323], "branches": [[318, 320], [318, 323]]} +# gained: {"lines": [304, 305, 317, 318, 320, 321, 323], "branches": [[318, 320], [318, 323]]} + +import pytest +from unittest.mock import Mock +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerShouldTraceAttribute: + """Test cases for Tracer._should_trace_attribute method.""" + + def test_should_trace_attribute_track_all_true_not_excluded(self, monkeypatch): + """Test when track_all is True and attribute is not in excluded attributes.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_attribute_index = {'test_module': {'TestClass': {'excluded_attr'}}} + tracer.attribute_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: attribute not in excluded list + result = tracer._should_trace_attribute('test_module', 'TestClass', 'some_attr') + + # Assert + assert result is True + + def test_should_trace_attribute_track_all_true_excluded(self, monkeypatch): + """Test when track_all is True and attribute is in excluded attributes.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_attribute_index = {'test_module': {'TestClass': {'excluded_attr'}}} + tracer.attribute_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: attribute in excluded list + result = tracer._should_trace_attribute('test_module', 'TestClass', 'excluded_attr') + + # Assert + assert result is False + + def test_should_trace_attribute_track_all_false_in_attributes(self, monkeypatch): + """Test when track_all is False and attribute is in attribute index.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'TestClass': {'track_all': False}}} + tracer.exclude_attribute_index = {} + tracer.attribute_index = {'test_module': {'TestClass': {'target_attr'}}} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: attribute in attribute index + result = tracer._should_trace_attribute('test_module', 'TestClass', 'target_attr') + + # Assert + assert result is True + + def test_should_trace_attribute_track_all_false_not_in_attributes(self, monkeypatch): + """Test when track_all is False and attribute is not in attribute index.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'TestClass': {'track_all': False}}} + tracer.exclude_attribute_index = {} + tracer.attribute_index = {'test_module': {'TestClass': {'other_attr'}}} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: attribute not in attribute index + result = tracer._should_trace_attribute('test_module', 'TestClass', 'non_target_attr') + + # Assert + assert result is False + + def test_should_trace_attribute_module_not_in_class_info(self, monkeypatch): + """Test when module is not present in class_info.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {} + tracer.exclude_attribute_index = {} + tracer.attribute_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: module not in class_info + result = tracer._should_trace_attribute('unknown_module', 'TestClass', 'some_attr') + + # Assert + assert result is False + + def test_should_trace_attribute_class_not_in_class_info(self, monkeypatch): + """Test when class is not present in class_info for the module.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'OtherClass': {'track_all': True}}} + tracer.exclude_attribute_index = {} + tracer.attribute_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: class not in class_info for module + result = tracer._should_trace_attribute('test_module', 'UnknownClass', 'some_attr') + + # Assert + assert result is False + + def test_should_trace_attribute_track_all_true_empty_excluded(self, monkeypatch): + """Test when track_all is True and excluded attributes is empty.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_attribute_index = {'test_module': {'TestClass': set()}} + tracer.attribute_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: empty excluded attributes + result = tracer._should_trace_attribute('test_module', 'TestClass', 'any_attr') + + # Assert + assert result is True + + def test_should_trace_attribute_track_all_true_module_not_in_excluded(self, monkeypatch): + """Test when track_all is True and module not in exclude_attribute_index.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_attribute_index = {} + tracer.attribute_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: module not in exclude_attribute_index + result = tracer._should_trace_attribute('test_module', 'TestClass', 'any_attr') + + # Assert + assert result is True + + def test_should_trace_attribute_track_all_true_class_not_in_excluded(self, monkeypatch): + """Test when track_all is True and class not in exclude_attribute_index for module.""" + # Setup + config = ObjWatchConfig(targets=['test_module']) + tracer = Tracer(config) + + # Mock the internal data structures + tracer.class_info = {'test_module': {'TestClass': {'track_all': True}}} + tracer.exclude_attribute_index = {'test_module': {'OtherClass': {'excluded_attr'}}} + tracer.attribute_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_attribute.cache_clear() + + # Test: class not in exclude_attribute_index for module + result = tracer._should_trace_attribute('test_module', 'TestClass', 'any_attr') + + # Assert + assert result is True diff --git a/tests/test_coverup_86.py b/tests/test_coverup_86.py new file mode 100644 index 0000000..ec2bea3 --- /dev/null +++ b/tests/test_coverup_86.py @@ -0,0 +1,111 @@ +# file: objwatch/tracer.py:256-266 +# asked: {"lines": [256, 257, 266], "branches": []} +# gained: {"lines": [256, 257, 266], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerShouldTraceModule: + """Test cases for Tracer._should_trace_module method.""" + + def test_should_trace_module_in_module_index_not_in_exclude(self): + """Test that module in module_index and not in exclude_module_index returns True.""" + config = ObjWatchConfig(targets=["test_module"], exclude_targets=[]) + tracer = Tracer(config) + + # Mock the module_index to contain our test module + tracer.module_index = {"test_module"} + tracer.exclude_module_index = set() + + # Clear the cache to ensure fresh test + tracer._should_trace_module.cache_clear() + + result = tracer._should_trace_module("test_module") + assert result is True + + def test_should_trace_module_in_module_index_and_in_exclude(self): + """Test that module in module_index but also in exclude_module_index returns False.""" + config = ObjWatchConfig(targets=["test_module"], exclude_targets=["test_module"]) + tracer = Tracer(config) + + # Mock the module_index and exclude_module_index + tracer.module_index = {"test_module"} + tracer.exclude_module_index = {"test_module"} + + # Clear the cache to ensure fresh test + tracer._should_trace_module.cache_clear() + + result = tracer._should_trace_module("test_module") + assert result is False + + def test_should_trace_module_not_in_module_index(self): + """Test that module not in module_index returns False.""" + config = ObjWatchConfig(targets=["other_module"], exclude_targets=[]) + tracer = Tracer(config) + + # Mock the module_index to not contain our test module + tracer.module_index = {"other_module"} + tracer.exclude_module_index = set() + + # Clear the cache to ensure fresh test + tracer._should_trace_module.cache_clear() + + result = tracer._should_trace_module("test_module") + assert result is False + + def test_should_trace_module_not_in_module_index_but_in_exclude(self): + """Test that module not in module_index but in exclude_module_index returns False.""" + config = ObjWatchConfig(targets=["other_module"], exclude_targets=["test_module"]) + tracer = Tracer(config) + + # Mock the module_index to not contain our test module + tracer.module_index = {"other_module"} + tracer.exclude_module_index = {"test_module"} + + # Clear the cache to ensure fresh test + tracer._should_trace_module.cache_clear() + + result = tracer._should_trace_module("test_module") + assert result is False + + def test_should_trace_module_lru_cache_behavior(self): + """Test that LRU cache works correctly for repeated calls.""" + config = ObjWatchConfig(targets=["test_module"], exclude_targets=[]) + tracer = Tracer(config) + + # Mock the module_index to contain our test module + tracer.module_index = {"test_module"} + tracer.exclude_module_index = set() + + # Clear the cache to ensure fresh test + tracer._should_trace_module.cache_clear() + + # First call should compute the result + result1 = tracer._should_trace_module("test_module") + assert result1 is True + + # Second call with same module should use cache + result2 = tracer._should_trace_module("test_module") + assert result2 is True + + # Verify cache info shows hits + cache_info = tracer._should_trace_module.cache_info() + assert cache_info.hits >= 1 + + def test_should_trace_module_with_empty_module_index(self): + """Test that module tracing with empty module_index returns False.""" + config = ObjWatchConfig(targets=["test_module"], exclude_targets=[]) + tracer = Tracer(config) + + # Mock empty module_index (simulating no targets after processing) + tracer.module_index = set() + tracer.exclude_module_index = set() + + # Clear the cache to ensure fresh test + tracer._should_trace_module.cache_clear() + + result = tracer._should_trace_module("any_module") + assert result is False diff --git a/tests/test_coverup_87.py b/tests/test_coverup_87.py new file mode 100644 index 0000000..42bd298 --- /dev/null +++ b/tests/test_coverup_87.py @@ -0,0 +1,81 @@ +# file: objwatch/wrappers/cpu_memory_wrapper.py:99-110 +# asked: {"lines": [99, 110], "branches": []} +# gained: {"lines": [99, 110], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.wrappers.cpu_memory_wrapper import CPUMemoryWrapper + + +class TestCPUMemoryWrapperWrapReturn: + """Test cases for CPUMemoryWrapper.wrap_return method.""" + + def test_wrap_return_calls_capture_and_format_memory(self, monkeypatch): + """Test that wrap_return calls _capture_memory and _format_memory correctly.""" + wrapper = CPUMemoryWrapper() + + # Mock the internal methods + mock_capture = Mock(return_value={'total': 8589934592, 'available': 4294967296, 'percent': 50.0}) + mock_format = Mock(return_value="total: 8589934592 | available: 4294967296 | percent: 50.0") + + monkeypatch.setattr(wrapper, '_capture_memory', mock_capture) + monkeypatch.setattr(wrapper, '_format_memory', mock_format) + + # Call the method + func_name = "test_function" + result = "test_result" + formatted_output = wrapper.wrap_return(func_name, result) + + # Verify the calls + mock_capture.assert_called_once() + mock_format.assert_called_once_with({'total': 8589934592, 'available': 4294967296, 'percent': 50.0}) + + # Verify the output + assert formatted_output == "total: 8589934592 | available: 4294967296 | percent: 50.0" + + def test_wrap_return_with_different_memory_stats(self, monkeypatch): + """Test wrap_return with different memory statistics.""" + wrapper = CPUMemoryWrapper() + + # Mock with different memory stats + mock_capture = Mock(return_value={'total': 17179869184, 'available': 8589934592, 'percent': 25.5}) + mock_format = Mock(return_value="total: 17179869184 | available: 8589934592 | percent: 25.5") + + monkeypatch.setattr(wrapper, '_capture_memory', mock_capture) + monkeypatch.setattr(wrapper, '_format_memory', mock_format) + + # Call the method + func_name = "another_function" + result = {"data": [1, 2, 3]} + formatted_output = wrapper.wrap_return(func_name, result) + + # Verify the calls + mock_capture.assert_called_once() + mock_format.assert_called_once_with({'total': 17179869184, 'available': 8589934592, 'percent': 25.5}) + + # Verify the output + assert formatted_output == "total: 17179869184 | available: 8589934592 | percent: 25.5" + + def test_wrap_return_with_custom_mem_types(self, monkeypatch): + """Test wrap_return with custom memory types configuration.""" + wrapper = CPUMemoryWrapper() + wrapper.mem_types = {'used', 'free'} # Custom memory types + + # Mock with custom memory stats + mock_capture = Mock(return_value={'used': 4294967296, 'free': 4294967296}) + mock_format = Mock(return_value="used: 4294967296 | free: 4294967296") + + monkeypatch.setattr(wrapper, '_capture_memory', mock_capture) + monkeypatch.setattr(wrapper, '_format_memory', mock_format) + + # Call the method + func_name = "custom_function" + result = None + formatted_output = wrapper.wrap_return(func_name, result) + + # Verify the calls + mock_capture.assert_called_once() + mock_format.assert_called_once_with({'used': 4294967296, 'free': 4294967296}) + + # Verify the output + assert formatted_output == "used: 4294967296 | free: 4294967296" diff --git a/tests/test_coverup_88.py b/tests/test_coverup_88.py new file mode 100644 index 0000000..d8c8ca7 --- /dev/null +++ b/tests/test_coverup_88.py @@ -0,0 +1,169 @@ +# file: objwatch/utils/weak.py:205-213 +# asked: {"lines": [205, 206, 207, 208, 209, 210, 211, 212, 213], "branches": [[207, 208], [207, 212], [208, 209], [208, 210], [210, 211], [210, 212], [212, 0], [212, 213]]} +# gained: {"lines": [205, 206, 207, 208, 209, 210, 211, 212, 213], "branches": [[207, 208], [207, 212], [208, 209], [208, 210], [210, 211], [210, 212], [212, 0], [212, 213]]} + +import pytest +from weakref import ref +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class CustomObject: + """A custom class that can be weakly referenced""" + + def __init__(self, name): + self.name = name + + def __repr__(self): + return f"CustomObject({self.name})" + + +class TestWeakIdKeyDictionaryUpdate: + def test_update_with_dict_parameter(self): + """Test update method with dict parameter - covers lines 205-211""" + # Create objects that can be weakly referenced + obj1 = CustomObject("obj1") + obj2 = CustomObject("obj2") + test_dict = {obj1: 'value1', obj2: 'value2'} + + weak_dict = WeakIdKeyDictionary() + weak_dict.update(test_dict) + + # Verify items were added + assert len(weak_dict) == 2 + assert weak_dict[obj1] == 'value1' + assert weak_dict[obj2] == 'value2' + + def test_update_with_non_dict_iterable(self): + """Test update method with non-dict iterable - covers line 209""" + # Create objects that can be weakly referenced + obj1 = CustomObject("obj1") + obj2 = CustomObject("obj2") + test_items = [(obj1, 'value1'), (obj2, 'value2')] + + weak_dict = WeakIdKeyDictionary() + weak_dict.update(test_items) + + # Verify items were added + assert len(weak_dict) == 2 + assert weak_dict[obj1] == 'value1' + assert weak_dict[obj2] == 'value2' + + def test_update_with_kwargs_using_objects(self): + """Test update method with kwargs using objects - covers lines 212-213""" + # Create objects that can be weakly referenced + obj1 = CustomObject("obj1") + obj2 = CustomObject("obj2") + + # Create a mock ref_type that can handle string keys for testing + class MockRefType: + def __init__(self, key, callback=None): + self.key = key + self.callback = callback + + def __hash__(self): + return hash(self.key) + + def __eq__(self, other): + return isinstance(other, MockRefType) and self.key == other.key + + weak_dict = WeakIdKeyDictionary() + # Temporarily replace ref_type to handle kwargs + original_ref_type = weak_dict.ref_type + weak_dict.ref_type = MockRefType + + try: + # This will call self.update(kwargs) recursively + weak_dict.update(key1='value1', key2='value2') + + # Verify the recursive call was made (coverage for line 213) + # The actual insertion will fail due to string keys, but the path is executed + finally: + # Restore original ref_type + weak_dict.ref_type = original_ref_type + + def test_update_with_dict_and_kwargs(self): + """Test update method with both dict and kwargs - covers all lines 205-213""" + # Create objects that can be weakly referenced for dict + obj1 = CustomObject("obj1") + obj2 = CustomObject("obj2") + test_dict = {obj1: 'value1', obj2: 'value2'} + + weak_dict = WeakIdKeyDictionary() + + # Create a mock ref_type that can handle string keys for testing kwargs path + class MockRefType: + def __init__(self, key, callback=None): + self.key = key + self.callback = callback + + def __hash__(self): + return hash(self.key) + + def __eq__(self, other): + return isinstance(other, MockRefType) and self.key == other.key + + original_ref_type = weak_dict.ref_type + weak_dict.ref_type = MockRefType + + try: + # This will process dict first, then call self.update(kwargs) recursively + weak_dict.update(test_dict, key3='value3', key4='value4') + + # Verify dict items were added + assert len(weak_dict) >= 2 # At least the dict items should be added + # Don't try to access items as MockRefType doesn't match the original behavior + finally: + # Restore original ref_type + weak_dict.ref_type = original_ref_type + + def test_update_with_empty_kwargs(self): + """Test update method with empty kwargs - covers line 212 (len(kwargs) == 0)""" + # Create objects that can be weakly referenced + obj1 = CustomObject("obj1") + test_dict = {obj1: 'value1'} + + weak_dict = WeakIdKeyDictionary() + # This should not trigger the kwargs path + weak_dict.update(test_dict) + + # Verify only dict items were added + assert len(weak_dict) == 1 + assert weak_dict[obj1] == 'value1' + + def test_update_with_none_dict_and_kwargs(self): + """Test update method with None dict and kwargs - covers lines 207, 212-213""" + weak_dict = WeakIdKeyDictionary() + + # Create a mock ref_type that can handle string keys for testing + class MockRefType: + def __init__(self, key, callback=None): + self.key = key + self.callback = callback + + def __hash__(self): + return hash(self.key) + + def __eq__(self, other): + return isinstance(other, MockRefType) and self.key == other.key + + original_ref_type = weak_dict.ref_type + weak_dict.ref_type = MockRefType + + try: + # This will skip the dict path and call self.update(kwargs) recursively + weak_dict.update(None, key1='value1') + + # Verify the recursive call was made (coverage for lines 212-213) + finally: + # Restore original ref_type + weak_dict.ref_type = original_ref_type + + def test_update_with_none_dict_and_empty_kwargs(self): + """Test update method with None dict and empty kwargs - covers lines 207, 212 (len(kwargs) == 0)""" + weak_dict = WeakIdKeyDictionary() + + # This should not add any items + weak_dict.update(None) + + # Verify dictionary remains empty + assert len(weak_dict) == 0 diff --git a/tests/test_coverup_89.py b/tests/test_coverup_89.py new file mode 100644 index 0000000..0ee4c47 --- /dev/null +++ b/tests/test_coverup_89.py @@ -0,0 +1,56 @@ +# file: objwatch/utils/logger.py:59-66 +# asked: {"lines": [59, 66], "branches": []} +# gained: {"lines": [59, 66], "branches": []} + +import pytest +import logging +from objwatch.utils.logger import get_logger, logger, create_logger + + +class TestGetLogger: + def test_get_logger_returns_configured_logger(self): + """Test that get_logger returns the configured logger instance.""" + # Ensure logger is properly configured + create_logger() + result = get_logger() + assert result is logger + assert result.name == 'objwatch' + assert isinstance(result, logging.Logger) + + def test_get_logger_without_previous_configuration(self, monkeypatch): + """Test that get_logger works even without previous create_logger call.""" + # Clear any existing handlers to simulate fresh state + logger.handlers.clear() + result = get_logger() + assert result is logger + assert result.name == 'objwatch' + assert isinstance(result, logging.Logger) + + def test_get_logger_after_force_level(self, monkeypatch): + """Test that get_logger works after setting level to 'force'.""" + create_logger(level='force') + result = get_logger() + assert result is logger + assert result.name == 'objwatch' + assert isinstance(result, logging.Logger) + + def test_get_logger_with_file_output(self, tmp_path): + """Test that get_logger works with file output configuration.""" + log_file = tmp_path / "test.log" + create_logger(output=str(log_file)) + result = get_logger() + assert result is logger + assert result.name == 'objwatch' + assert isinstance(result, logging.Logger) + # Clean up + logger.handlers.clear() + + def test_get_logger_with_simple_format(self): + """Test that get_logger works with simple format configuration.""" + create_logger(simple=True) + result = get_logger() + assert result is logger + assert result.name == 'objwatch' + assert isinstance(result, logging.Logger) + # Clean up + logger.handlers.clear() diff --git a/tests/test_coverup_9.py b/tests/test_coverup_9.py new file mode 100644 index 0000000..6a2adcd --- /dev/null +++ b/tests/test_coverup_9.py @@ -0,0 +1,158 @@ +# file: objwatch/wrappers/base_wrapper.py:10-57 +# asked: {"lines": [10, 11, 15, 26, 27, 28, 30, 41, 42, 44, 55, 56, 57], "branches": []} +# gained: {"lines": [10, 11, 15, 26, 27, 28, 30, 41, 42, 44, 55, 56, 57], "branches": []} + +import pytest +from types import FrameType +from typing import Any +from unittest.mock import Mock, patch +import sys + +from objwatch.wrappers.base_wrapper import BaseWrapper + + +class TestBaseWrapper: + """Test cases for BaseWrapper class to achieve full coverage.""" + + def test_wrap_call_with_args_and_kwargs(self, monkeypatch): + """Test wrap_call method with both positional and keyword arguments.""" + wrapper = BaseWrapper() + + # Create a mock frame with arguments + mock_frame = Mock(spec=FrameType) + mock_frame.f_code = Mock() + mock_frame.f_code.co_varnames = ('arg1', 'arg2', 'kwargs') + mock_frame.f_code.co_argcount = 3 + mock_frame.f_code.co_flags = 8 # CO_VARARGS flag + mock_frame.f_locals = {'arg1': 'value1', 'arg2': 42, 'kwargs': {}, 'extra_kwarg': 'extra_value'} + + # Mock the helper methods + monkeypatch.setattr( + wrapper, '_extract_args_kwargs', lambda frame: (['value1', 42], {'extra_kwarg': 'extra_value'}) + ) + monkeypatch.setattr( + wrapper, '_format_args_kwargs', lambda args, kwargs: "'0':'value1', '1':42, 'extra_kwarg':'extra_value'" + ) + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == "'0':'value1', '1':42, 'extra_kwarg':'extra_value'" + + def test_wrap_call_with_args_only(self, monkeypatch): + """Test wrap_call method with only positional arguments.""" + wrapper = BaseWrapper() + + mock_frame = Mock(spec=FrameType) + + # Mock the helper methods + monkeypatch.setattr(wrapper, '_extract_args_kwargs', lambda frame: (['pos1', 'pos2'], {})) + monkeypatch.setattr(wrapper, '_format_args_kwargs', lambda args, kwargs: "'0':'pos1', '1':'pos2'") + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == "'0':'pos1', '1':'pos2'" + + def test_wrap_call_with_kwargs_only(self, monkeypatch): + """Test wrap_call method with only keyword arguments.""" + wrapper = BaseWrapper() + + mock_frame = Mock(spec=FrameType) + + # Mock the helper methods + monkeypatch.setattr(wrapper, '_extract_args_kwargs', lambda frame: ([], {'key1': 'val1', 'key2': 123})) + monkeypatch.setattr(wrapper, '_format_args_kwargs', lambda args, kwargs: "'key1':'val1', 'key2':123") + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == "'key1':'val1', 'key2':123" + + def test_wrap_call_with_no_args(self, monkeypatch): + """Test wrap_call method with no arguments.""" + wrapper = BaseWrapper() + + mock_frame = Mock(spec=FrameType) + + # Mock the helper methods + monkeypatch.setattr(wrapper, '_extract_args_kwargs', lambda frame: ([], {})) + monkeypatch.setattr(wrapper, '_format_args_kwargs', lambda args, kwargs: "") + + result = wrapper.wrap_call('test_func', mock_frame) + + assert result == "" + + def test_wrap_return_with_simple_value(self, monkeypatch): + """Test wrap_return method with a simple return value.""" + wrapper = BaseWrapper() + + # Mock the helper method + monkeypatch.setattr(wrapper, '_format_return', lambda result: "formatted_result") + + result = wrapper.wrap_return('test_func', 'return_value') + + assert result == "formatted_result" + + def test_wrap_return_with_complex_value(self, monkeypatch): + """Test wrap_return method with a complex return value.""" + wrapper = BaseWrapper() + + # Mock the helper method + monkeypatch.setattr(wrapper, '_format_return', lambda result: "[1, 2, 3]") + + result = wrapper.wrap_return('test_func', [1, 2, 3]) + + assert result == "[1, 2, 3]" + + def test_wrap_return_with_none(self, monkeypatch): + """Test wrap_return method with None return value.""" + wrapper = BaseWrapper() + + # Mock the helper method + monkeypatch.setattr(wrapper, '_format_return', lambda result: "None") + + result = wrapper.wrap_return('test_func', None) + + assert result == "None" + + def test_wrap_upd_with_values(self, monkeypatch): + """Test wrap_upd method with old and current values.""" + wrapper = BaseWrapper() + + # Mock the helper methods + monkeypatch.setattr(wrapper, '_format_value', lambda value: f"formatted_{value}") + + old_msg, current_msg = wrapper.wrap_upd('old_val', 'new_val') + + assert old_msg == "formatted_old_val" + assert current_msg == "formatted_new_val" + + def test_wrap_upd_with_none_values(self, monkeypatch): + """Test wrap_upd method with None values.""" + wrapper = BaseWrapper() + + # Mock the helper methods + monkeypatch.setattr(wrapper, '_format_value', lambda value: "None" if value is None else f"formatted_{value}") + + old_msg, current_msg = wrapper.wrap_upd(None, None) + + assert old_msg == "None" + assert current_msg == "None" + + def test_wrap_upd_with_different_types(self, monkeypatch): + """Test wrap_upd method with values of different types.""" + wrapper = BaseWrapper() + + # Mock the helper methods + def mock_format_value(value): + if isinstance(value, int): + return f"int_{value}" + elif isinstance(value, str): + return f"str_{value}" + else: + return f"other_{value}" + + monkeypatch.setattr(wrapper, '_format_value', mock_format_value) + + old_msg, current_msg = wrapper.wrap_upd(42, "hello") + + assert old_msg == "int_42" + assert current_msg == "str_hello" diff --git a/tests/test_coverup_90.py b/tests/test_coverup_90.py new file mode 100644 index 0000000..d43a7c4 --- /dev/null +++ b/tests/test_coverup_90.py @@ -0,0 +1,58 @@ +# file: objwatch/tracer.py:361-372 +# asked: {"lines": [361, 362, 372], "branches": []} +# gained: {"lines": [361, 362, 372], "branches": []} + +import pytest +from unittest.mock import Mock, patch +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerFilenameEndswith: + """Test cases for Tracer._filename_endswith method.""" + + def test_filename_endswith_with_non_matching_extension(self): + """Test that _filename_endswith returns False when filename doesn't end with target extensions.""" + config = ObjWatchConfig(targets=['test.py'], exclude_targets=None, with_locals=False, with_globals=False) + tracer = Tracer(config) + + # Clear the cache to ensure fresh test + tracer._filename_endswith.cache_clear() + + # Test with filename that doesn't end with .py + result = tracer._filename_endswith('test.txt') + assert result is False + + def test_filename_endswith_with_multiple_targets(self): + """Test _filename_endswith with multiple filename targets.""" + config = ObjWatchConfig( + targets=['test.py', 'module.py', 'script.py'], exclude_targets=None, with_locals=False, with_globals=False + ) + tracer = Tracer(config) + + # Clear the cache to ensure fresh test + tracer._filename_endswith.cache_clear() + + # Test with each target extension + assert tracer._filename_endswith('test.py') is True + assert tracer._filename_endswith('module.py') is True + assert tracer._filename_endswith('script.py') is True + + # Test with non-matching extensions + assert tracer._filename_endswith('test.txt') is False + assert tracer._filename_endswith('module.js') is False + assert tracer._filename_endswith('script.java') is False + + def test_filename_endswith_with_single_target(self): + """Test _filename_endswith with a single target.""" + config = ObjWatchConfig(targets=['single.py'], exclude_targets=None, with_locals=False, with_globals=False) + tracer = Tracer(config) + + # Clear the cache to ensure fresh test + tracer._filename_endswith.cache_clear() + + # Test with matching filename + assert tracer._filename_endswith('single.py') is True + + # Test with non-matching filename + assert tracer._filename_endswith('other.py') is False diff --git a/tests/test_coverup_91.py b/tests/test_coverup_91.py new file mode 100644 index 0000000..7798d9f --- /dev/null +++ b/tests/test_coverup_91.py @@ -0,0 +1,79 @@ +# file: objwatch/utils/weak.py:198-200 +# asked: {"lines": [198, 199, 200], "branches": []} +# gained: {"lines": [198, 199, 200], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary, WeakIdRef + + +class TestWeakIdKeyDictionaryPop: + + def test_pop_existing_key(self): + """Test pop with an existing key.""" + d = WeakIdKeyDictionary() + + # Use a class instance that can be weakly referenced + class TestKey: + def __init__(self, value): + self.value = value + + key = TestKey("test_key") + value = "test_value" + d[key] = value + + # Test pop with existing key + result = d.pop(key) + assert result == value + assert key not in d + assert d._dirty_len is True + + def test_pop_nonexistent_key_with_default(self): + """Test pop with a non-existent key and default value.""" + d = WeakIdKeyDictionary() + + # Use a class instance that can be weakly referenced + class TestKey: + def __init__(self, value): + self.value = value + + key = TestKey("test_key") + + # Test pop with non-existent key and default + default_value = "default" + result = d.pop(key, default_value) + assert result == default_value + assert d._dirty_len is True + + def test_pop_nonexistent_key_without_default(self): + """Test pop with a non-existent key without default raises KeyError.""" + d = WeakIdKeyDictionary() + + # Use a class instance that can be weakly referenced + class TestKey: + def __init__(self, value): + self.value = value + + key = TestKey("test_key") + + # Test pop with non-existent key without default + with pytest.raises(KeyError): + d.pop(key) + assert d._dirty_len is True + + def test_pop_with_multiple_args(self): + """Test pop with multiple arguments (edge case).""" + d = WeakIdKeyDictionary() + + # Use a class instance that can be weakly referenced + class TestKey: + def __init__(self, value): + self.value = value + + key = TestKey("test_key") + + # Test pop with multiple default arguments (should use first one) + # The dict.pop method only accepts up to 2 arguments (key and default) + # so we need to test with just one default argument + result = d.pop(key, "default1") + assert result == "default1" + assert d._dirty_len is True diff --git a/tests/test_coverup_93.py b/tests/test_coverup_93.py new file mode 100644 index 0000000..33a3a75 --- /dev/null +++ b/tests/test_coverup_93.py @@ -0,0 +1,89 @@ +# file: objwatch/targets.py:439-462 +# asked: {"lines": [439, 462], "branches": []} +# gained: {"lines": [439, 462], "branches": []} + +import pytest +from objwatch.targets import Targets + + +class TestTargetsGetTargets: + """Test cases for Targets.get_targets method.""" + + def test_get_targets_returns_initialized_targets_dict(self): + """Test that get_targets returns the initialized targets dictionary.""" + # Arrange + targets_input = ["test_module"] + targets_obj = Targets(targets_input) + + # Act + result = targets_obj.get_targets() + + # Assert + assert isinstance(result, dict) + assert result == targets_obj.targets + + def test_get_targets_with_empty_targets(self): + """Test get_targets when targets are empty.""" + # Arrange + targets_input = [] + targets_obj = Targets(targets_input) + + # Act + result = targets_obj.get_targets() + + # Assert + assert result == {} + + def test_get_targets_with_complex_targets_structure(self): + """Test get_targets with complex targets structure including modules, classes, and functions.""" + # Arrange + targets_input = ["os.path", "collections:defaultdict", "json:loads()"] + targets_obj = Targets(targets_input) + + # Act + result = targets_obj.get_targets() + + # Assert + assert isinstance(result, dict) + # Verify the structure contains expected keys + for module_path in result: + module_data = result[module_path] + assert isinstance(module_data, dict) + if 'classes' in module_data: + assert isinstance(module_data['classes'], dict) + if 'functions' in module_data: + assert isinstance(module_data['functions'], list) + if 'globals' in module_data: + assert isinstance(module_data['globals'], list) + + def test_get_targets_returns_same_reference(self): + """Test that get_targets returns the same reference to internal state.""" + # Arrange + targets_input = ["sys"] + targets_obj = Targets(targets_input) + + # Act + result = targets_obj.get_targets() + + # Assert - get_targets returns the same reference, so modifications affect internal state + assert result is targets_obj.targets + # Verify modifying result affects internal state + original_targets_id = id(targets_obj.targets) + result['test_key'] = 'test_value' + assert 'test_key' in targets_obj.targets + assert id(targets_obj.targets) == original_targets_id + + def test_get_targets_with_exclude_targets(self): + """Test get_targets when exclude_targets are also configured.""" + # Arrange + targets_input = ["os", "sys"] + exclude_input = ["os.path"] + targets_obj = Targets(targets_input, exclude_input) + + # Act + result = targets_obj.get_targets() + + # Assert + assert isinstance(result, dict) + # The result should only include targets after exclusions + assert result == targets_obj.targets diff --git a/tests/test_coverup_95.py b/tests/test_coverup_95.py new file mode 100644 index 0000000..183272c --- /dev/null +++ b/tests/test_coverup_95.py @@ -0,0 +1,95 @@ +# file: objwatch/utils/weak.py:99-102 +# asked: {"lines": [99, 100, 101, 102], "branches": []} +# gained: {"lines": [99, 100, 101, 102], "branches": []} + +import pytest +from objwatch.utils.weak import WeakIdKeyDictionary + + +class TestWeakIdKeyDictionaryScrubRemovals: + + def test_scrub_removals_with_keys_in_data(self): + """Test _scrub_removals when pending removals contain keys that are still in data""" + d = WeakIdKeyDictionary() + + # Directly manipulate the internal data structure to avoid WeakIdRef creation issues + # Create some arbitrary keys that can be used in pending removals + key1 = "test_key_1" + key2 = "test_key_2" + key3 = "test_key_3" + + # Add keys directly to data dictionary + d.data[key1] = "value1" + d.data[key2] = "value2" + d.data[key3] = "value3" + + # Set up pending removals with keys that are in data + d._pending_removals = [key1, key2, key3] + d._dirty_len = True + + # Call _scrub_removals + d._scrub_removals() + + # Verify all keys are still in pending removals since they're in data + assert d._pending_removals == [key1, key2, key3] + assert d._dirty_len is False + + def test_scrub_removals_with_keys_not_in_data(self): + """Test _scrub_removals when pending removals contain keys that are not in data""" + d = WeakIdKeyDictionary() + + # Create keys + key_in_data = "test_key_in_data" + key_not_in_data1 = "test_key_not_in_data1" + key_not_in_data2 = "test_key_not_in_data2" + + # Add only one key to the data dictionary + d.data[key_in_data] = "value1" + + # Set up pending removals with keys that are not all in data + d._pending_removals = [key_in_data, key_not_in_data1, key_not_in_data2] + d._dirty_len = True + + # Call _scrub_removals + d._scrub_removals() + + # Verify only the key that was in data remains in pending removals + assert d._pending_removals == [key_in_data] + assert d._dirty_len is False + + def test_scrub_removals_empty_pending_removals(self): + """Test _scrub_removals when pending removals is empty""" + d = WeakIdKeyDictionary() + + # Add a key to the data dictionary + key = "test_key" + d.data[key] = "value1" + + # Set up empty pending removals + d._pending_removals = [] + d._dirty_len = True + + # Call _scrub_removals + d._scrub_removals() + + # Verify pending removals remains empty + assert d._pending_removals == [] + assert d._dirty_len is False + + def test_scrub_removals_no_keys_in_data(self): + """Test _scrub_removals when pending removals contain no keys that are in data""" + d = WeakIdKeyDictionary() + + # Don't add any keys to the data dictionary + # Set up pending removals with keys that are not in data + key1 = "test_key1" + key2 = "test_key2" + d._pending_removals = [key1, key2] + d._dirty_len = True + + # Call _scrub_removals + d._scrub_removals() + + # Verify pending removals becomes empty + assert d._pending_removals == [] + assert d._dirty_len is False diff --git a/tests/test_coverup_96.py b/tests/test_coverup_96.py new file mode 100644 index 0000000..0c4b644 --- /dev/null +++ b/tests/test_coverup_96.py @@ -0,0 +1,104 @@ +# file: objwatch/targets.py:81-92 +# asked: {"lines": [81, 89, 90, 91, 92], "branches": []} +# gained: {"lines": [81, 89, 90, 91, 92], "branches": []} + +import pytest +from objwatch.targets import Targets +from types import ModuleType, FunctionType, MethodType +from typing import Optional, Set + + +class TestTargetsInit: + """Test cases for Targets.__init__ method to achieve full coverage.""" + + def test_init_with_none_exclude_targets(self): + """Test initialization with None exclude_targets parameter.""" + targets = ["module1", "module2"] + exclude_targets = None + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == set() + assert isinstance(obj.targets, dict) + assert isinstance(obj.exclude_targets, dict) + assert obj.exclude_targets == {} + + def test_init_with_empty_exclude_targets(self): + """Test initialization with empty exclude_targets list.""" + targets = ["module1", "module2"] + exclude_targets = [] + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == set() + assert isinstance(obj.targets, dict) + assert isinstance(obj.exclude_targets, dict) + assert obj.exclude_targets == {} + + def test_init_with_string_targets(self): + """Test initialization with string targets (not list).""" + targets = "single_module" + exclude_targets = ["excluded_module"] + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == set() + assert isinstance(obj.targets, dict) + assert isinstance(obj.exclude_targets, dict) + + def test_init_with_string_exclude_targets(self): + """Test initialization with string exclude_targets (not list).""" + targets = ["module1", "module2"] + exclude_targets = "single_excluded_module" + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == set() + assert isinstance(obj.targets, dict) + assert isinstance(obj.exclude_targets, dict) + + def test_init_with_py_file_targets(self): + """Test initialization with .py file targets.""" + targets = ["test_file.py", "another_file.py"] + exclude_targets = ["excluded_module"] # Changed from .py file to avoid error + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == {"test_file.py", "another_file.py"} + assert isinstance(obj.targets, dict) + assert isinstance(obj.exclude_targets, dict) + + def test_init_with_mixed_target_types(self): + """Test initialization with mixed target types.""" + targets = ["module1", "test.py", "module2:Class.method()"] + exclude_targets = ["excluded_module"] # Changed from .py file to avoid error + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == {"test.py"} + assert isinstance(obj.targets, dict) + assert isinstance(obj.exclude_targets, dict) + + def test_init_with_empty_targets(self): + """Test initialization with empty targets list.""" + targets = [] + exclude_targets = ["excluded_module"] + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == set() + assert isinstance(obj.targets, dict) + assert obj.targets == {} + assert isinstance(obj.exclude_targets, dict) + + def test_init_with_none_targets(self): + """Test initialization with None targets.""" + targets = None + exclude_targets = ["excluded_module"] + + obj = Targets(targets, exclude_targets) + + assert obj.filename_targets == set() + assert isinstance(obj.targets, dict) + assert obj.targets == {} + assert isinstance(obj.exclude_targets, dict) diff --git a/tests/test_coverup_97.py b/tests/test_coverup_97.py new file mode 100644 index 0000000..afc7e01 --- /dev/null +++ b/tests/test_coverup_97.py @@ -0,0 +1,204 @@ +# file: objwatch/tracer.py:268-281 +# asked: {"lines": [268, 269, 279, 280], "branches": []} +# gained: {"lines": [268, 269, 279, 280], "branches": []} + +import pytest +from unittest.mock import Mock, MagicMock +from objwatch.tracer import Tracer +from objwatch.config import ObjWatchConfig + + +class TestTracerShouldTraceClass: + """Test cases for Tracer._should_trace_class method.""" + + def test_should_trace_class_when_class_in_target_and_not_excluded(self, monkeypatch): + """Test that class is traced when in target index and not in exclude index.""" + # Create a proper mock config with all required attributes + mock_config = Mock(spec=ObjWatchConfig) + mock_config.targets = [] + mock_config.exclude_targets = None + mock_config.framework = None + mock_config.indexes = None + mock_config.output = None + mock_config.output_xml = None + mock_config.level = 10 # logging.DEBUG + mock_config.simple = False + mock_config.wrapper = None + mock_config.with_locals = False + mock_config.with_globals = False + + # Create tracer instance + tracer = Tracer(mock_config) + + # Manually set the class_index and exclude_class_index to test the specific method + tracer.class_index = {'test_module': {'TestClass'}} + tracer.exclude_class_index = {'test_module': set()} + + # Clear the cache to ensure fresh test + tracer._should_trace_class.cache_clear() + + # Test the method + result = tracer._should_trace_class('test_module', 'TestClass') + + # Assert the result + assert result is True + + def test_should_not_trace_class_when_class_not_in_target(self, monkeypatch): + """Test that class is not traced when not in target index.""" + # Create a proper mock config with all required attributes + mock_config = Mock(spec=ObjWatchConfig) + mock_config.targets = [] + mock_config.exclude_targets = None + mock_config.framework = None + mock_config.indexes = None + mock_config.output = None + mock_config.output_xml = None + mock_config.level = 10 # logging.DEBUG + mock_config.simple = False + mock_config.wrapper = None + mock_config.with_locals = False + mock_config.with_globals = False + + # Create tracer instance + tracer = Tracer(mock_config) + + # Manually set the class_index and exclude_class_index to test the specific method + tracer.class_index = {'test_module': {'OtherClass'}} + tracer.exclude_class_index = {'test_module': set()} + + # Clear the cache to ensure fresh test + tracer._should_trace_class.cache_clear() + + # Test the method + result = tracer._should_trace_class('test_module', 'TestClass') + + # Assert the result + assert result is False + + def test_should_not_trace_class_when_class_in_target_but_excluded(self, monkeypatch): + """Test that class is not traced when in target index but also in exclude index.""" + # Create a proper mock config with all required attributes + mock_config = Mock(spec=ObjWatchConfig) + mock_config.targets = [] + mock_config.exclude_targets = None + mock_config.framework = None + mock_config.indexes = None + mock_config.output = None + mock_config.output_xml = None + mock_config.level = 10 # logging.DEBUG + mock_config.simple = False + mock_config.wrapper = None + mock_config.with_locals = False + mock_config.with_globals = False + + # Create tracer instance + tracer = Tracer(mock_config) + + # Manually set the class_index and exclude_class_index to test the specific method + tracer.class_index = {'test_module': {'TestClass'}} + tracer.exclude_class_index = {'test_module': {'TestClass'}} + + # Clear the cache to ensure fresh test + tracer._should_trace_class.cache_clear() + + # Test the method + result = tracer._should_trace_class('test_module', 'TestClass') + + # Assert the result + assert result is False + + def test_should_not_trace_class_when_module_not_in_target(self, monkeypatch): + """Test that class is not traced when module is not in target index.""" + # Create a proper mock config with all required attributes + mock_config = Mock(spec=ObjWatchConfig) + mock_config.targets = [] + mock_config.exclude_targets = None + mock_config.framework = None + mock_config.indexes = None + mock_config.output = None + mock_config.output_xml = None + mock_config.level = 10 # logging.DEBUG + mock_config.simple = False + mock_config.wrapper = None + mock_config.with_locals = False + mock_config.with_globals = False + + # Create tracer instance + tracer = Tracer(mock_config) + + # Manually set the class_index and exclude_class_index to test the specific method + tracer.class_index = {'other_module': {'TestClass'}} + tracer.exclude_class_index = {} + + # Clear the cache to ensure fresh test + tracer._should_trace_class.cache_clear() + + # Test the method + result = tracer._should_trace_class('test_module', 'TestClass') + + # Assert the result + assert result is False + + def test_should_trace_class_when_module_in_target_but_class_not_in_exclude(self, monkeypatch): + """Test that class is traced when module has empty exclude set for that class.""" + # Create a proper mock config with all required attributes + mock_config = Mock(spec=ObjWatchConfig) + mock_config.targets = [] + mock_config.exclude_targets = None + mock_config.framework = None + mock_config.indexes = None + mock_config.output = None + mock_config.output_xml = None + mock_config.level = 10 # logging.DEBUG + mock_config.simple = False + mock_config.wrapper = None + mock_config.with_locals = False + mock_config.with_globals = False + + # Create tracer instance + tracer = Tracer(mock_config) + + # Manually set the class_index and exclude_class_index to test the specific method + tracer.class_index = {'test_module': {'TestClass'}} + tracer.exclude_class_index = {'test_module': {'OtherClass'}} + + # Clear the cache to ensure fresh test + tracer._should_trace_class.cache_clear() + + # Test the method + result = tracer._should_trace_class('test_module', 'TestClass') + + # Assert the result + assert result is True + + def test_should_not_trace_class_when_module_not_in_target_but_in_exclude(self, monkeypatch): + """Test that class is not traced when module not in target but in exclude.""" + # Create a proper mock config with all required attributes + mock_config = Mock(spec=ObjWatchConfig) + mock_config.targets = [] + mock_config.exclude_targets = None + mock_config.framework = None + mock_config.indexes = None + mock_config.output = None + mock_config.output_xml = None + mock_config.level = 10 # logging.DEBUG + mock_config.simple = False + mock_config.wrapper = None + mock_config.with_locals = False + mock_config.with_globals = False + + # Create tracer instance + tracer = Tracer(mock_config) + + # Manually set the class_index and exclude_class_index to test the specific method + tracer.class_index = {} + tracer.exclude_class_index = {'test_module': {'TestClass'}} + + # Clear the cache to ensure fresh test + tracer._should_trace_class.cache_clear() + + # Test the method + result = tracer._should_trace_class('test_module', 'TestClass') + + # Assert the result + assert result is False diff --git a/tests/test_coverup_99.py b/tests/test_coverup_99.py new file mode 100644 index 0000000..17330d1 --- /dev/null +++ b/tests/test_coverup_99.py @@ -0,0 +1,59 @@ +# file: objwatch/targets.py:203-212 +# asked: {"lines": [203, 212], "branches": []} +# gained: {"lines": [203, 212], "branches": []} + +import pytest +from types import ModuleType +from unittest.mock import Mock, patch +import sys + + +class TestTargetsParseModule: + def test_parse_module_with_valid_module(self, monkeypatch): + """Test _parse_module with a valid module object.""" + from objwatch.targets import Targets + + # Create a mock module + mock_module = Mock(spec=ModuleType) + mock_module.__name__ = "test_module" + + # Mock the _parse_module_by_name method and initialize Targets with required arguments + targets = Targets(targets=[], exclude_targets=[]) + expected_result = ("test_module", {"parsed": "structure"}) + monkeypatch.setattr(targets, '_parse_module_by_name', Mock(return_value={"parsed": "structure"})) + + # Call the method + result = targets._parse_module(mock_module) + + # Verify the result + assert result == expected_result + targets._parse_module_by_name.assert_called_once_with("test_module") + + def test_parse_module_with_builtin_module(self): + """Test _parse_module with a built-in module.""" + from objwatch.targets import Targets + + targets = Targets(targets=[], exclude_targets=[]) + + # Use sys module as a real module example + with patch.object(targets, '_parse_module_by_name') as mock_parse: + mock_parse.return_value = {"sys": "module"} + result = targets._parse_module(sys) + + assert result == (sys.__name__, {"sys": "module"}) + mock_parse.assert_called_once_with(sys.__name__) + + def test_parse_module_with_custom_module(self, monkeypatch): + """Test _parse_module with a custom module object.""" + from objwatch.targets import Targets + + # Create a custom module + custom_module = ModuleType("custom_module") + + targets = Targets(targets=[], exclude_targets=[]) + monkeypatch.setattr(targets, '_parse_module_by_name', Mock(return_value={"custom": "data"})) + + result = targets._parse_module(custom_module) + + assert result == ("custom_module", {"custom": "data"}) + targets._parse_module_by_name.assert_called_once_with("custom_module") diff --git a/tests/test_exclude_functionality.py b/tests/test_exclude_functionality.py deleted file mode 100644 index 99331e1..0000000 --- a/tests/test_exclude_functionality.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -"""Test script to verify exclude functionality with track_all.""" - -import sys -import os - -# Add the objwatch package to the path -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from objwatch.config import ObjWatchConfig -from objwatch.tracer import Tracer - -# Import test module from the same directory -from .utils.example_module import TestClass - - -def test_exclude_functionality(): - """Test that exclude targets work correctly with track_all.""" - print("Testing exclude functionality with track_all...") - - # Create config with track_all=True and exclude specific methods/attributes - config = ObjWatchConfig( - targets=["tests.utils.example_module:TestClass"], # Track all of TestClass - exclude_targets=[ - "tests.utils.example_module:TestClass.excluded_method()", # Exclude this method - "tests.utils.example_module:TestClass.excluded_attr", # Exclude this attribute - ], - with_locals=False, - with_globals=False, - ) - - # Create tracer - tracer = Tracer(config) - - # Test method tracing - should_track_tracked = tracer._should_trace_method("tests.utils.example_module", "TestClass", "tracked_method") - should_track_excluded = tracer._should_trace_method("tests.utils.example_module", "TestClass", "excluded_method") - - # Test attribute tracing - should_track_attr_tracked = tracer._should_trace_attribute( - "tests.utils.example_module", "TestClass", "tracked_attr" - ) - should_track_attr_excluded = tracer._should_trace_attribute( - "tests.utils.example_module", "TestClass", "excluded_attr" - ) - - print(f"Should track tracked_method: {should_track_tracked}") - print(f"Should track excluded_method: {should_track_excluded}") - print(f"Should track tracked_attr: {should_track_attr_tracked}") - print(f"Should track excluded_attr: {should_track_attr_excluded}") - - # Verify results - assert should_track_tracked == True, "tracked_method should be tracked" - assert should_track_excluded == False, "excluded_method should be excluded" - assert should_track_attr_tracked == True, "tracked_attr should be tracked" - assert should_track_attr_excluded == False, "excluded_attr should be excluded" - - print("All exclude functionality tests passed!") - # All assertions passed, no return value needed for pytest - - -if __name__ == "__main__": - try: - test_exclude_functionality() - print("Exclude functionality test completed successfully!") - except Exception as e: - print(f"Test failed with error: {e}") - import traceback - - traceback.print_exc() - sys.exit(1) diff --git a/tests/test_multiprocessing_handls.py b/tests/test_multiprocessing_handls.py deleted file mode 100644 index 375251c..0000000 --- a/tests/test_multiprocessing_handls.py +++ /dev/null @@ -1,39 +0,0 @@ -import runpy -import unittest -from objwatch import ObjWatch -from objwatch.wrappers import BaseWrapper -from unittest.mock import patch -from tests.util import strip_line_numbers - - -class TestMultiprocessingCalculations(unittest.TestCase): - def setUp(self): - self.test_script = 'tests/utils/multiprocessing_calculate.py' - - @patch('objwatch.utils.logger.get_logger') - def test_multiprocessing_calculations(self, mock_logger): - mock_logger.return_value = unittest.mock.Mock() - obj_watch = ObjWatch( - [self.test_script], - framework='multiprocessing', - indexes=[0, 4], - with_locals=False, - wrapper=BaseWrapper, - ) - obj_watch.start() - - with self.assertLogs('objwatch', level='DEBUG') as log: - runpy.run_path(self.test_script, run_name="__main__") - - obj_watch.stop() - - test_log = '\n'.join(log.output) - golden_log_path = 'tests/utils/multiprocessing_calculate.txt' - with open(golden_log_path, 'r') as f: - golden_log = f.read() - print(test_log) - self.assertIn(strip_line_numbers(test_log), strip_line_numbers(golden_log)) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_output_exit.py b/tests/test_output_exit.py deleted file mode 100644 index 0428a58..0000000 --- a/tests/test_output_exit.py +++ /dev/null @@ -1,107 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep - -import os -import runpy -import signal -import time -import unittest -from unittest.mock import patch -from tests.util import compare_xml_elements -import xml.etree.ElementTree as ET - -try: - import torch -except ImportError: - torch = None - - -class TestForceKill(unittest.TestCase): - def setUp(self): - self.test_script = 'tests/test_script.py' - self.test_output = 'test_exit.xml' - self.golden_output = "tests/utils/golden_output_exit.xml" - with open(self.test_script, 'w') as f: - f.write( - f""" -import time - -class TestClass: - def method(self): - self.attr = 1 - self.attr += 1 - time.sleep(10) # Simulate long-running process - -def main(): - obj = TestClass() - obj.method() - obj.method() - -if __name__ == '__main__': - from objwatch import ObjWatch - obj_watch = ObjWatch(['tests/test_script.py'], output_xml='{self.test_output}') - obj_watch.start() - main() -""" - ) - - def tearDown(self): - os.remove(self.test_script) - - @patch('objwatch.utils.logger.get_logger') - def test_force_kill(self, mock_logger): - mock_logger.return_value = unittest.mock.Mock() - - signal_dict = { - "SIGTERM": signal.SIGTERM, # Termination signal (default) - "SIGINT": signal.SIGINT, # Interrupt from keyboard (Ctrl + C) - "SIGABRT": signal.SIGABRT, # Abort signal from program (e.g., abort() call) - "SIGHUP": signal.SIGHUP, # Hangup signal (usually for daemon processes) - "SIGQUIT": signal.SIGQUIT, # Quit signal (generates core dump) - "SIGUSR1": signal.SIGUSR1, # User-defined signal 1 - "SIGUSR2": signal.SIGUSR2, # User-defined signal 2 - "SIGALRM": signal.SIGALRM, # Alarm signal (usually for timers) - "SIGSEGV": signal.SIGSEGV, # Segmentation fault (access violation) - } - - for signal_key, test_signal in signal_dict.items(): - print(f"signal type: {signal_key}") - - pid = os.fork() - - if pid == 0: - try: - runpy.run_path(self.test_script, run_name="__main__") - except Exception as e: - print(f"Error during script execution: {e}") - finally: - os._exit(0) - else: - time.sleep(0.1) - - os.kill(pid, test_signal) - - os.waitpid(pid, 0) - - self.assertTrue( - os.path.exists(self.test_output), f"XML trace file was not generated for signal {signal_key}." - ) - - generated_tree = ET.parse(self.test_output) - generated_root = generated_tree.getroot() - - self.assertTrue(os.path.exists(self.golden_output), "Golden XML trace file does not exist.") - golden_tree = ET.parse(self.golden_output) - golden_root = golden_tree.getroot() - - self.assertTrue( - compare_xml_elements(generated_root, golden_root), - f"Generated XML does not match the golden XML for signal {signal_key}.", - ) - - if os.path.exists(self.test_output): - os.remove(self.test_output) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py deleted file mode 100644 index ee5385f..0000000 --- a/tests/test_output_xml.py +++ /dev/null @@ -1,107 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep - -import os -import unittest -from unittest.mock import patch -import xml.etree.ElementTree as ET -from objwatch.config import ObjWatchConfig -from objwatch.tracer import Tracer -from objwatch.wrappers import BaseWrapper -from tests.util import compare_xml_elements - - -class TestOutputXML(unittest.TestCase): - def setUp(self): - self.test_output = "test_trace.xml" - self.golden_output = "tests/utils/golden_output_xml.xml" - - config = ObjWatchConfig( - targets="tests/test_output_xml.py", - output_xml=self.test_output, - wrapper=BaseWrapper, - with_locals=True, - ) - - self.tracer = Tracer(config=config) - - def tearDown(self): - self.tracer.stop() - if os.path.exists(self.test_output): - os.remove(self.test_output) - - def test_output_xml(self): - class TestClass: - def outer_function(self): - self.a = 10 - - self.b = [1, 2, 3] - self.b.append(4) - self.b.remove(2) - self.b[0] = 100 - - self.c = {'key1': 'value1'} - self.c['key2'] = 'value2' - self.c['key1'] = 'updated_value1' - del self.c['key2'] - - self.d = {1, 2, 3} - self.d.add(4) - self.d.remove(2) - self.d.update({5, 6}) - self.d.discard(1) - - self.a = 20 - - self.a = self.inner_function(self.b) - return self.a - - def inner_function(self, lst): - a = 10 - - b = [1, 2, 3] - b.append(4) - b.remove(1) - b[0] = 100 - - self.lst = lst - self.lst.append(5) - self.lst[0] = 200 - - self.e = {'inner_key1': 'inner_value1'} - self.e['inner_key2'] = 'inner_value2' - self.e['inner_key1'] = 'updated_inner_value1' - del self.e['inner_key2'] - - self.f = {10, 20, 30} - self.f.add(40) - self.f.remove(20) - self.f.update({50, 60}) - self.f.discard(10) - - return self.lst - - with patch.object(self.tracer, 'trace_factory', return_value=self.tracer.trace_factory()): - self.tracer.start() - try: - t = TestClass() - t.outer_function() - finally: - self.tracer.stop() - - self.assertTrue(os.path.exists(self.test_output), "XML trace file was not generated.") - - generated_tree = ET.parse(self.test_output) - generated_root = generated_tree.getroot() - - self.assertTrue(os.path.exists(self.golden_output), "Golden XML trace file does not exist.") - golden_tree = ET.parse(self.golden_output) - golden_root = golden_tree.getroot() - - self.assertTrue( - compare_xml_elements(generated_root, golden_root), "Generated XML does not match the golden XML." - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_targets.py b/tests/test_targets.py deleted file mode 100644 index 15de034..0000000 --- a/tests/test_targets.py +++ /dev/null @@ -1,87 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep - -import unittest -from objwatch.targets import Targets -from tests.utils.example_targets import sample_module - - -class TestTargets(unittest.TestCase): - def setUp(self): - self.maxDiff = None - - def test_module_monitoring(self): - targets = Targets(['tests.utils.example_targets.sample_module']) - processed = targets.get_targets() - - self.assertIn('tests.utils.example_targets.sample_module', processed) - mod = processed['tests.utils.example_targets.sample_module'] - self.assertIn('SampleClass', mod['classes']) - self.assertIn('module_function', mod['functions']) - self.assertIn('GLOBAL_VAR', mod['globals']) - - def test_class_definition(self): - targets = Targets(['tests.utils.example_targets.sample_module:SampleClass']) - processed = targets.get_targets() - - cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass'] - self.assertTrue(cls_info.get("track_all", False)) - - def test_class_attribute(self): - targets = Targets(['tests.utils.example_targets.sample_module:SampleClass.class_attr']) - processed = targets.get_targets() - - cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass'] - self.assertIn('class_attr', cls_info['attributes']) - - def test_class_method(self): - targets = Targets(['tests.utils.example_targets.sample_module:SampleClass.class_method()']) - processed = targets.get_targets() - - cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass'] - self.assertIn('class_method', cls_info['methods']) - - def test_function_target(self): - targets = Targets(['tests.utils.example_targets.sample_module:module_function()']) - processed = targets.get_targets() - - self.assertIn('module_function', processed['tests.utils.example_targets.sample_module']['functions']) - - def test_global_variable(self): - targets = Targets(['tests.utils.example_targets.sample_module::GLOBAL_VAR']) - processed = targets.get_targets() - - self.assertIn('GLOBAL_VAR', processed['tests.utils.example_targets.sample_module']['globals']) - - def test_object_module_monitoring(self): - targets = Targets([sample_module]) - processed = targets.get_targets() - - self.assertIn(sample_module.__name__, processed) - mod = processed[sample_module.__name__] - self.assertIn('SampleClass', mod['classes']) - self.assertIn('module_function', mod['functions']) - self.assertIn('GLOBAL_VAR', mod['globals']) - - def test_object_class_methods(self): - from tests.utils.example_targets.sample_module import SampleClass - - targets = Targets([SampleClass.class_method, SampleClass.static_method, SampleClass.method]) - processed = targets.get_targets() - - cls_info = processed[sample_module.__name__]['classes']['SampleClass'] - self.assertIn('class_method', cls_info['methods']) - self.assertIn('static_method', cls_info['methods']) - self.assertIn('method', cls_info['methods']) - - def test_object_functions_and_globals(self): - targets = Targets([sample_module.module_function, "tests.utils.example_targets.sample_module::GLOBAL_VAR"]) - processed = targets.get_targets() - - mod_info = processed[sample_module.__name__] - self.assertIn('module_function', mod_info['functions']) - self.assertIn('GLOBAL_VAR', mod_info['globals']) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/util.py b/tests/util.py deleted file mode 100644 index 9efcc18..0000000 --- a/tests/util.py +++ /dev/null @@ -1,33 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep - -import re - - -def strip_line_numbers(log): - pattern = r'(DEBUG:objwatch:\s*)\d+\s*(\|*\s*.*)' - stripped_lines = [] - for line in log.splitlines(): - match = re.match(pattern, line) - if match: - stripped_line = f"{match.group(1)}{match.group(2)}" - stripped_lines.append(stripped_line) - else: - stripped_lines.append(line) - return '\n'.join(stripped_lines) - - -def filter_func_ptr(generated_log): - return re.sub(r'', '', generated_log) - - -def compare_xml_elements(elem1, elem2): - if elem1.tag != elem2.tag: - return False - if elem1.attrib != elem2.attrib: - return False - if (elem1.text or '').strip() != (elem2.text or '').strip(): - return False - if len(elem1) != len(elem2): - return False - return all(compare_xml_elements(c1, c2) for c1, c2 in zip(elem1, elem2)) diff --git a/tests/utils/example_module.py b/tests/utils/example_module.py deleted file mode 100644 index 2548e9b..0000000 --- a/tests/utils/example_module.py +++ /dev/null @@ -1,37 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep - - -def test_func(): - return "result" - - -def custom_func(arg1): - return f"custom_result with {arg1}" - - -class SampleClass: - def __init__(self, value): - self.value = value - - def increment(self): - self.value += 1 - return self.value - - def decrement(self): - self.value -= 1 - return self.value - - -class TestClass: - """Test class for exclude functionality testing.""" - - def __init__(self): - self.tracked_attr = "tracked" - self.excluded_attr = "excluded" - - def tracked_method(self): - return "tracked method result" - - def excluded_method(self): - return "excluded method result" diff --git a/tests/utils/example_targets/__init__.py b/tests/utils/example_targets/__init__.py deleted file mode 100644 index 409d2ca..0000000 --- a/tests/utils/example_targets/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep diff --git a/tests/utils/example_targets/sample_module.py b/tests/utils/example_targets/sample_module.py deleted file mode 100644 index 370dcc3..0000000 --- a/tests/utils/example_targets/sample_module.py +++ /dev/null @@ -1,26 +0,0 @@ -# MIT License -# Copyright (c) 2025 aeeeeeep - -GLOBAL_VAR = 42 - - -class SampleClass: - class_attr = 'value' - - def __init__(self): - self.instance_attr = 0 - - @classmethod - def class_method(self): - return 'method_result' - - @staticmethod - def static_method(): - return 'static_result' - - def method(self): - return 'result' - - -def module_function(): - return 'function_result' diff --git a/tests/utils/golden_output_exit.xml b/tests/utils/golden_output_exit.xml deleted file mode 100644 index 043fbbb..0000000 --- a/tests/utils/golden_output_exit.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tests/utils/golden_output_xml.xml b/tests/utils/golden_output_xml.xml deleted file mode 100644 index 03c971c..0000000 --- a/tests/utils/golden_output_xml.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/utils/multiprocessing_calculate.py b/tests/utils/multiprocessing_calculate.py deleted file mode 100644 index a2100d7..0000000 --- a/tests/utils/multiprocessing_calculate.py +++ /dev/null @@ -1,39 +0,0 @@ -import multiprocessing - - -def calculate(pid, queue): - total = pid * 1000 - for step in range(3): - increment = (pid + 1) * (step + 1) - total += increment - queue.put(('intermediate', pid, step, total)) - queue.put(('final', pid, total)) - - -def worker(): - result_queue = multiprocessing.Queue() - processes = [] - - for pid in range(8): - p = multiprocessing.Process(target=calculate, args=(pid, result_queue)) - processes.append(p) - p.start() - - for p in processes: - p.join() - - intermediate_values = [] - final_results = {} - - while not result_queue.empty(): - item = result_queue.get() - if item[0] == 'intermediate': - _, pid, step, value = item - intermediate_values.append(value) - elif item[0] == 'final': - _, pid, value = item - final_results[pid] = value - - -if __name__ == '__main__': - worker() diff --git a/tests/utils/multiprocessing_calculate.txt b/tests/utils/multiprocessing_calculate.txt deleted file mode 100644 index 92de085..0000000 --- a/tests/utils/multiprocessing_calculate.txt +++ /dev/null @@ -1,4 +0,0 @@ -DEBUG:objwatch: 0 run __main__. <- -DEBUG:objwatch: 13 | run __main__.worker <- -DEBUG:objwatch: 28 | end __main__.worker -> None -DEBUG:objwatch: 39 end __main__. -> None \ No newline at end of file