-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_resource_management.py
More file actions
292 lines (222 loc) · 9.89 KB
/
test_resource_management.py
File metadata and controls
292 lines (222 loc) · 9.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import concurrent.futures
import gc
import logging
import os
import sys
import tempfile
import time
import weakref
# Add the parent directory to sys.path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pytest
from pythonLogs import (
LogLevel,
)
from pythonLogs.basic_log import BasicLog as BasicLogImpl
from pythonLogs.core.factory import (
LoggerFactory,
LoggerType,
clear_logger_registry,
get_registered_loggers,
shutdown_logger,
)
@pytest.mark.skipif(
sys.platform == "win32",
reason="Windows file locking issues with TemporaryDirectory - see equivalent Windows-specific test file",
)
class TestResourceManagement:
"""Test resource management functionality."""
@pytest.fixture(autouse=True)
def setup_temp_dir(self):
"""Set up test fixtures before each test method."""
# Clear any existing loggers
clear_logger_registry()
# Create temporary directory for log files using context manager
with tempfile.TemporaryDirectory() as temp_dir:
self.temp_dir = temp_dir
self.log_file = "resource_test.log"
yield
# Clear registry after each test
clear_logger_registry()
def test_factory_registry_cleanup(self):
"""Test that factory registry cleanup properly closes handlers."""
logger_name = "test_registry_cleanup"
# Create logger through factory (returns logging.Logger)
logger = LoggerFactory.create_size_rotating_logger(
name=logger_name,
directory=self.temp_dir,
filenames=[self.log_file],
maxmbytes=1,
)
# Add to registry
LoggerFactory._logger_registry[logger_name] = (logger, time.time())
# Verify logger has handlers
assert len(logger.handlers) > 0
initial_handler_count = len(logger.handlers)
# Clear registry
LoggerFactory.clear_registry()
# Verify handlers were closed and removed
assert len(logger.handlers) == 0
assert initial_handler_count > 0 # Ensure we actually had handlers to clean up
assert len(LoggerFactory._logger_registry) == 0
def test_shutdown_specific_logger(self):
"""Test shutting down a specific logger."""
logger1_name = "test_logger_1"
logger2_name = "test_logger_2"
# Create two loggers using factory (properly adds to registry)
logger1 = LoggerFactory.get_or_create_logger(LoggerType.BASIC, name=logger1_name, level=LogLevel.INFO.value)
logger2 = LoggerFactory.get_or_create_logger(LoggerType.BASIC, name=logger2_name, level=LogLevel.DEBUG.value)
# Verify both are in registry
assert len(get_registered_loggers()) == 2
# Shutdown only logger1
result = shutdown_logger(logger1_name)
assert result is True
# Verify logger1 was removed and cleaned up
assert logger1_name not in get_registered_loggers()
assert len(logger1.handlers) == 0
# Verify logger2 is still active
assert logger2_name in get_registered_loggers()
assert len(get_registered_loggers()) == 1
def test_shutdown_nonexistent_logger(self):
"""Test shutting down a logger that doesn't exist."""
result = shutdown_logger("nonexistent_logger")
assert result is False
def test_handler_cleanup_static_method(self):
"""Test the static cleanup method directly."""
# Create a logger with handlers
logger = logging.getLogger("test_static_cleanup")
handler1 = logging.StreamHandler()
handler2 = logging.StreamHandler()
logger.addHandler(handler1)
logger.addHandler(handler2)
assert len(logger.handlers) == 2
# Use static cleanup method from internal class
BasicLogImpl.cleanup_logger(logger)
# Verify all handlers were cleaned up
assert len(logger.handlers) == 0
def test_handler_cleanup_with_errors(self):
"""Test handler cleanup handles errors gracefully."""
logger = logging.getLogger("test_error_cleanup")
# Create a mock handler that raises an error on close
class ErrorHandler(logging.Handler):
def close(self):
raise OSError("Mock error during close")
error_handler = ErrorHandler()
normal_handler = logging.StreamHandler()
logger.addHandler(error_handler)
logger.addHandler(normal_handler)
assert len(logger.handlers) == 2
# Cleanup should handle errors and still remove handlers
BasicLogImpl.cleanup_logger(logger)
# All handlers should be removed despite errors
assert len(logger.handlers) == 0
def test_registry_clear_with_file_handlers(self):
"""Test registry cleanup with file handlers."""
logger_name = "test_file_handlers"
# Create logger with file handlers (returns logging.Logger)
logger = LoggerFactory.create_size_rotating_logger(
name=logger_name,
directory=self.temp_dir,
filenames=[self.log_file, "second.log"],
maxmbytes=1,
streamhandler=True, # Add stream handler too
)
# Add to registry
LoggerFactory._logger_registry[logger_name] = (logger, time.time())
# Write some data to verify handlers are working
logger.info("Test message before cleanup")
# Verify we have multiple handlers
file_handlers = [h for h in logger.handlers if hasattr(h, 'baseFilename')]
stream_handlers = [h for h in logger.handlers if isinstance(h, logging.StreamHandler)]
assert len(file_handlers) == 2 # Two file handlers
assert len(stream_handlers) > 0 # At least one stream handler
# Clear registry
clear_logger_registry()
# Verify all handlers cleaned up
assert len(logger.handlers) == 0
assert len(get_registered_loggers()) == 0
def test_resource_cleanup_performance(self):
"""Test that resource cleanup doesn't cause performance issues."""
num_loggers = 10
logger_names = [f"perf_test_logger_{i}" for i in range(num_loggers)]
# Create multiple loggers using factory
start_time = time.time()
for name in logger_names:
logger = LoggerFactory.create_size_rotating_logger(
name=name,
directory=self.temp_dir,
filenames=[f"{name}.log"],
maxmbytes=1,
)
LoggerFactory._logger_registry[name] = (logger, time.time())
creation_time = time.time() - start_time
# Verify all created
assert len(get_registered_loggers()) == num_loggers
# Clear all at once
cleanup_start = time.time()
clear_logger_registry()
cleanup_time = time.time() - cleanup_start
# Verify cleanup completed
assert len(get_registered_loggers()) == 0
# Performance should be reasonable (less than 1 second for 10 loggers)
assert cleanup_time < 1.0
print(f"Created {num_loggers} loggers in {creation_time:.4f}s")
print(f"Cleaned up {num_loggers} loggers in {cleanup_time:.4f}s")
def test_memory_usage_after_cleanup(self):
"""Test that memory is properly released after cleanup."""
logger_name = "memory_test_logger"
# Create logger using factory (returns logging.Logger)
logger = LoggerFactory.create_size_rotating_logger(
name=logger_name,
directory=self.temp_dir,
filenames=[self.log_file],
maxmbytes=1,
)
# Add to registry
LoggerFactory._logger_registry[logger_name] = (logger, time.time())
# Create weak reference to track if logger is garbage collected
logger_weakref = weakref.ref(logger)
handler_weakrefs = [weakref.ref(h) for h in logger.handlers]
# Clear local reference
del logger
# Logger should still exist due to registry
assert logger_weakref() is not None
# Handlers should also still exist
assert all(ref() is not None for ref in handler_weakrefs)
# Clear registry
clear_logger_registry()
# Force garbage collection
gc.collect()
# Logger should be garbage collected
# Note: This test might be flaky depending on Python's garbage collector,
# but it helps verify we're not holding unnecessary references
print(f"Logger weakref after cleanup: {logger_weakref()}")
def test_concurrent_cleanup(self):
"""Test resource cleanup works correctly with concurrent access."""
def create_and_cleanup_logger(index):
"""Create a logger and immediately clean it up."""
logger_name = f"concurrent_test_{index}"
# Use factory to create logger (returns logging.Logger)
logger = LoggerFactory.create_basic_logger(name=logger_name, level=LogLevel.INFO.value)
# Add to registry
LoggerFactory._logger_registry[logger_name] = (logger, time.time())
# Small delay to increase chance of concurrent access
time.sleep(0.01)
# Shutdown this specific logger
return shutdown_logger(logger_name)
# Create multiple threads doing concurrent operations
num_threads = 5
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = []
for i in range(num_threads):
futures.append(executor.submit(create_and_cleanup_logger, i))
# Wait for all to complete
results = []
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
# All operations should succeed
assert all(results)
# The Registry should be empty
assert len(get_registered_loggers()) == 0
if __name__ == "__main__":
pytest.main([__file__])