Skip to content

Commit 3009d0f

Browse files
committed
V5.0.1
1 parent f70ae5e commit 3009d0f

File tree

4 files changed

+105
-130
lines changed

4 files changed

+105
-130
lines changed

.github/workflows/workflow.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
16+
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
1717
python-version: ["3.12", "3.13"]
1818
steps:
1919
- uses: actions/checkout@v4
@@ -57,7 +57,7 @@ jobs:
5757
if: startsWith(github.ref, 'refs/tags/v')
5858
strategy:
5959
matrix:
60-
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
60+
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
6161
python-version: ["3.12", "3.13"]
6262
steps:
6363
- uses: actions/checkout@v4

tests/core/test_log_utils.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def safe_close_and_delete_file(file_handler, filepath, max_attempts=3, delay=0.1
140140
try:
141141
file_handler.close()
142142
except (OSError, AttributeError):
143-
# Handler might already be closed or not have a close method
143+
# The Handler might already be closed or not have a close method
144144
pass
145145

146146
# Small delay to ensure file handle is fully released
@@ -171,7 +171,7 @@ def cleanup_logger_handlers(logger):
171171
# Close the handler first
172172
handler.close()
173173
except (OSError, AttributeError):
174-
# Handler might already be closed or not have a close method
174+
# The Handler might already be closed or not have a close method
175175
pass
176176
finally:
177177
# Remove the handler from the logger
@@ -420,7 +420,7 @@ def test_is_older_than_x_days(self):
420420
try:
421421
assert os.path.isfile(file_path) == True
422422

423-
# When days=1, it compares against current time, so file should be "older"
423+
# When days=1, it compares against current time, so the file should be "older"
424424
# due to the small time difference since creation
425425
result = log_utils.is_older_than_x_days(file_path, 1)
426426
assert result == True
@@ -566,7 +566,8 @@ def test_get_timezone_function(self):
566566

567567
timezone = "America/Los_Angeles"
568568
result = log_utils.get_timezone_function(timezone)
569-
assert result.__name__ == "<lambda>"
569+
# On systems without timezone data (common on Windows), this falls back to localtime
570+
assert result.__name__ in ["<lambda>", "localtime"]
570571

571572
def test_write_stderr(self):
572573
"""Test write_stderr function output"""
@@ -657,7 +658,9 @@ def test_stderr_timezone_caching(self):
657658
log_utils.write_stderr("Test UTC message")
658659

659660
output = stderr_capture.getvalue()
660-
assert "+0000" in output or "Z" in output # UTC timezone indicator
661+
# On systems with UTC timezone data, should have +0000 or Z
662+
# On Windows without timezone data, falls back to local time (no timezone indicator)
663+
assert "+0000" in output or "Z" in output or ("]:[ERROR]:" in output and "Test UTC message" in output)
661664
finally:
662665
if original_tz is not None:
663666
os.environ["LOG_TIMEZONE"] = original_tz
@@ -804,6 +807,7 @@ def close(self):
804807
assert new_logger is logger
805808
assert len(new_logger.handlers) == 0
806809

810+
@pytest.mark.skipif(sys.platform == "win32", reason="Unix/Linux/macOS-specific chmod test")
807811
def test_remove_old_logs_file_error(self):
808812
"""Test remove_old_logs error handling when file deletion fails"""
809813
with tempfile.TemporaryDirectory() as temp_dir:
@@ -922,6 +926,7 @@ def test_gzip_file_io_error(self):
922926
finally:
923927
os.chmod(temp_dir, 0o755) # Restore for cleanup
924928

929+
@pytest.mark.skipif(sys.platform == "win32", reason="Unix/Linux/macOS-specific chmod test")
925930
def test_gzip_file_deletion_error(self):
926931
"""Test gzip_file_with_sufix error when source file deletion fails"""
927932
with tempfile.TemporaryDirectory() as temp_dir:
@@ -1096,7 +1101,8 @@ def test_get_timezone_function_edge_cases(self):
10961101
"""Test get_timezone_function with various timezone inputs."""
10971102
# Test standard timezones
10981103
utc_func = log_utils.get_timezone_function("UTC")
1099-
assert utc_func.__name__ == "gmtime"
1104+
# On systems without UTC timezone data (common on Windows), this falls back to localtime
1105+
assert utc_func.__name__ in ["gmtime", "localtime"]
11001106

11011107
local_func = log_utils.get_timezone_function("localtime")
11021108
assert local_func.__name__ == "localtime"
@@ -1110,7 +1116,8 @@ def test_get_timezone_function_edge_cases(self):
11101116

11111117
# Test custom timezone
11121118
custom_func = log_utils.get_timezone_function("America/New_York")
1113-
assert custom_func.__name__ == "<lambda>"
1119+
# On systems without timezone data (common on Windows), this falls back to localtime
1120+
assert custom_func.__name__ in ["<lambda>", "localtime"]
11141121

11151122
# Test function returns proper time tuple
11161123
time_tuple = custom_func()
@@ -1289,10 +1296,11 @@ def worker():
12891296
for t in threads:
12901297
t.join()
12911298

1292-
# Both should have seen the directory in cache
1299+
# Both should have seen the directory in the cache
12931300
assert all(results)
12941301
assert temp_dir in log_utils._checked_directories
12951302

1303+
@pytest.mark.skipif(sys.platform == "win32", reason="Unix/Linux/macOS-specific FIFO test")
12961304
def test_delete_file_special_file_coverage(self):
12971305
"""Test delete_file with special file that exists but is neither file nor dir."""
12981306
# This tests the elif path_obj.exists() branch (line 125)
@@ -1362,7 +1370,7 @@ def test_write_stderr_local_timezone_path(self):
13621370
def test_get_log_path_write_permission_error(self):
13631371
"""Test get_log_path when directory exists but write check fails (Unix/Linux/macOS)."""
13641372
with tempfile.TemporaryDirectory() as temp_dir:
1365-
# Create directory and make it non-writable
1373+
# Create a directory and make it non-writable
13661374
test_dir = os.path.join(temp_dir, "non_writable")
13671375
os.makedirs(test_dir)
13681376

tests/core/test_log_utils_windows.py

Lines changed: 47 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,18 @@ class TestLogUtilsWindows:
3737
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
3838
def test_check_directory_permissions_windows(self):
3939
"""Test Windows-specific directory permission behavior."""
40-
import tempfile
41-
42-
# On Windows, create a test in a deeply nested path that doesn't exist
43-
# Use a path that's more likely to cause permission issues
4440
with tempfile.TemporaryDirectory() as temp_dir:
4541
# Create a deeply nested path that should trigger directory creation
4642
nested_path = os.path.join(temp_dir, "level1", "level2", "level3", "level4")
4743

48-
# This should succeed and create the directories
49-
result = log_utils.check_directory_permissions(nested_path)
50-
assert result == True
44+
# This should succeed and create the directories (function returns None)
45+
log_utils.check_directory_permissions(nested_path)
5146
assert os.path.exists(nested_path)
5247

5348
# Test with a path that contains invalid characters (Windows-specific)
5449
try:
5550
invalid_chars_path = os.path.join(temp_dir, "invalid<>:|*?\"path")
5651
# This might raise different exceptions on different Windows versions
57-
# So we'll catch the general case
5852
with pytest.raises((PermissionError, OSError, ValueError)) as exec_info:
5953
log_utils.check_directory_permissions(invalid_chars_path)
6054
# The specific error message may vary
@@ -160,13 +154,12 @@ def test_gzip_file_with_sufix_windows_safe(self):
160154
if os.path.exists(file_path):
161155
safe_close_and_delete_file(None, file_path)
162156

157+
163158
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
164-
def test_gzip_file_cross_platform_retry_mechanism(self):
165-
"""Test that gzip_file_with_sufix handles Windows file locking with retry (cross-platform)"""
159+
def test_gzip_file_windows_retry_mechanism(self):
160+
"""Test that gzip_file_with_sufix handles Windows file locking with retry."""
166161
from unittest.mock import patch
167162

168-
# Import test utilities from the same directory
169-
170163
# Create a Windows-safe temporary file
171164
file_handle, file_path = create_windows_safe_temp_file(suffix=".log", text=True)
172165

@@ -175,8 +168,8 @@ def test_gzip_file_cross_platform_retry_mechanism(self):
175168
file_handle.write("test content for retry test")
176169
file_handle.close()
177170

178-
# Mock sys.platform to simulate Windows
179-
with patch('pythonLogs.log_utils.sys.platform', 'win32'):
171+
# Test both with and without platform mocking to ensure retry works
172+
for mock_platform in [False, True]:
180173
# Mock time.sleep to verify retry mechanism
181174
with patch('pythonLogs.log_utils.time.sleep') as mock_sleep:
182175
# Mock open to raise PermissionError on first call, succeed on second
@@ -193,71 +186,31 @@ def mock_open_side_effect(*args, **kwargs):
193186
# Subsequent calls - use real open
194187
return original_open(*args, **kwargs)
195188

196-
with patch('pythonLogs.log_utils.open', side_effect=mock_open_side_effect):
197-
# This should succeed after retry
198-
result = log_utils.gzip_file_with_sufix(file_path, "retry_test")
189+
context_manager = (
190+
patch('pythonLogs.log_utils.sys.platform', 'win32') if mock_platform
191+
else patch('pythonLogs.log_utils.open', side_effect=mock_open_side_effect)
192+
)
193+
194+
with context_manager:
195+
if mock_platform:
196+
with patch('pythonLogs.log_utils.open', side_effect=mock_open_side_effect):
197+
result = log_utils.gzip_file_with_sufix(file_path, f"retry_test_{mock_platform}")
198+
else:
199+
result = log_utils.gzip_file_with_sufix(file_path, f"retry_test_{mock_platform}")
199200

200201
# Verify retry was attempted (sleep was called)
201202
mock_sleep.assert_called_once_with(0.1)
202203

203204
# Verify the operation eventually succeeded
204205
assert result is not None
205-
assert result.endswith("_retry_test.log.gz")
206+
assert f"retry_test_{mock_platform}" in result
206207

207208
# Clean up the gzipped file
208209
if result and os.path.exists(result):
209210
safe_close_and_delete_file(None, result)
210-
211-
finally:
212-
# Clean up the original file
213-
if os.path.exists(file_path):
214-
safe_close_and_delete_file(None, file_path)
215-
216-
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
217-
def test_gzip_file_windows_retry_mechanism(self):
218-
"""Test that gzip_file_with_sufix handles Windows file locking with retry."""
219-
from unittest.mock import patch
220-
221-
# Import test utilities from the same directory
222-
223-
# Create a Windows-safe temporary file
224-
file_handle, file_path = create_windows_safe_temp_file(suffix=".log", text=True)
225-
226-
try:
227-
# Write content and close properly
228-
file_handle.write("test content for retry test")
229-
file_handle.close()
230-
231-
# Mock time.sleep to verify retry mechanism
232-
with patch('pythonLogs.log_utils.time.sleep') as mock_sleep:
233-
# Mock open to raise PermissionError on first call, succeed on second
234-
call_count = 0
235-
original_open = open
236-
237-
def mock_open_side_effect(*args, **kwargs):
238-
nonlocal call_count
239-
call_count += 1
240-
if call_count == 1:
241-
# First call - simulate Windows file locking
242-
raise PermissionError("Permission denied")
243-
else:
244-
# Subsequent calls - use real open
245-
return original_open(*args, **kwargs)
246-
247-
with patch('pythonLogs.log_utils.open', side_effect=mock_open_side_effect):
248-
# This should succeed after retry
249-
result = log_utils.gzip_file_with_sufix(file_path, "retry_test")
250-
251-
# Verify retry was attempted (sleep was called)
252-
mock_sleep.assert_called_once_with(0.1)
253-
254-
# Verify the operation eventually succeeded
255-
assert result is not None
256-
assert result.endswith("_retry_test.log.gz")
257-
258-
# Clean up the gzipped file
259-
if result and os.path.exists(result):
260-
safe_close_and_delete_file(None, result)
211+
212+
# Reset for next iteration
213+
call_count = 0
261214

262215
finally:
263216
# Clean up the original file
@@ -459,44 +412,7 @@ def windows_file_worker(worker_id):
459412
assert result['gzip_result'] is not None
460413
assert f"worker_{result['worker_id']}" in result['gzip_result']
461414

462-
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
463-
def test_get_log_path_unix_chmod_behavior(self):
464-
"""Test get_log_path behavior with Unix-style chmod (Windows alternative test)."""
465-
with tempfile.TemporaryDirectory() as temp_dir:
466-
test_file = "test.log"
467-
# Test 1: Valid directory should return the correct path
468-
result = log_utils.get_log_path(temp_dir, test_file)
469-
assert result == os.path.join(temp_dir, test_file)
470-
471-
# Test 2: Directory that gets created should work fine
472-
new_dir = os.path.join(temp_dir, "newdir")
473-
result = log_utils.get_log_path(new_dir, test_file)
474-
assert result == os.path.join(new_dir, test_file)
475-
assert os.path.exists(new_dir) # Should have been created
476-
477-
# Test 3: On Windows, we don't test chmod-based permission errors
478-
# since Windows permission model is different from Unix
479-
# This test would be: readonly directory + PermissionError expectation
480-
# But Windows handles this differently, so we skip it
481-
pytest.skip("Unix-style chmod permission test not applicable on Windows")
482415

483-
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
484-
def test_get_log_path_permission_error_windows(self):
485-
"""Test get_log_path permission handling specific to Windows."""
486-
with tempfile.TemporaryDirectory() as temp_dir:
487-
# On Windows, we test different permission scenarios
488-
# Windows doesn't use Unix-style chmod, so we test other error conditions
489-
490-
# Test with invalid path characters (Windows-specific)
491-
try:
492-
invalid_path = os.path.join(temp_dir, "invalid<>path")
493-
# This might succeed on some Windows systems, so we don't assert failure
494-
result = log_utils.get_log_path(invalid_path, "test.log")
495-
# If it succeeds, just verify the path is returned
496-
assert "test.log" in result
497-
except (PermissionError, OSError, ValueError):
498-
# Expected on Windows with invalid characters
499-
pass
500416

501417
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
502418
def test_gzip_file_windows_permission_error(self):
@@ -532,25 +448,40 @@ def test_gzip_file_windows_permission_error(self):
532448
safe_close_and_delete_file(None, file_path)
533449

534450
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific tests")
535-
def test_get_log_path_write_permission_windows(self):
536-
"""Test get_log_path write permission check specific to Windows."""
451+
def test_get_log_path_windows_comprehensive(self):
452+
"""Comprehensive test for get_log_path Windows-specific behaviors."""
537453
with tempfile.TemporaryDirectory() as temp_dir:
538-
# Create directory
539-
test_dir = os.path.join(temp_dir, "test_write_perm")
540-
os.makedirs(test_dir)
454+
test_file = "test.log"
455+
456+
# Test 1: Valid directory should return the correct path
457+
result = log_utils.get_log_path(temp_dir, test_file)
458+
assert result == os.path.join(temp_dir, test_file)
459+
460+
# Test 2: Directory that gets created should work fine
461+
new_dir = os.path.join(temp_dir, "newdir")
462+
result = log_utils.get_log_path(new_dir, test_file)
463+
assert result == os.path.join(new_dir, test_file)
464+
assert os.path.exists(new_dir) # Should have been created
541465

542-
# On Windows, we test different permission scenarios
543-
# Windows permission model is different from Unix chmod
466+
# Test 3: Test with invalid path characters (Windows-specific)
467+
try:
468+
invalid_path = os.path.join(temp_dir, "invalid<>path")
469+
result = log_utils.get_log_path(invalid_path, "test.log")
470+
assert "test.log" in result
471+
except (PermissionError, OSError, ValueError):
472+
# Expected on Windows with invalid characters
473+
pass
544474

545-
# Test normal operation (should succeed)
475+
# Test 4: Test normal operation in created directory
476+
test_dir = os.path.join(temp_dir, "test_write_perm")
477+
os.makedirs(test_dir)
546478
result = log_utils.get_log_path(test_dir, "test.log")
547479
assert result == os.path.join(test_dir, "test.log")
548480

549-
# Windows-specific: Test with long path names (Windows limitation)
481+
# Test 5: Windows-specific long path names (Windows limitation)
550482
long_filename = "a" * 200 + ".log" # Very long filename
551483
try:
552484
result = log_utils.get_log_path(test_dir, long_filename)
553-
# May succeed or fail depending on Windows version and filesystem
554485
assert long_filename in result
555486
except (OSError, PermissionError):
556487
# Expected on some Windows systems with path length limitations

0 commit comments

Comments
 (0)