-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathtest_wrappers.py
More file actions
117 lines (96 loc) · 4.56 KB
/
test_wrappers.py
File metadata and controls
117 lines (96 loc) · 4.56 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
import warnings
def no_warnings(func):
def wrapper_no_warnings(*args, **kwargs):
with warnings.catch_warnings(record=True) as record:
func(*args, **kwargs)
if len(record) > 0:
raise AssertionError(
"Warnings were raised: " + ", ".join([str(w) for w in record])
)
return wrapper_no_warnings
def check_warnings(
expected_warnings=None, # List of warnings that MUST be present
forbidden_warnings=None, # List of warnings that MUST NOT be present
match_type="contains", # "exact", "contains", "regex"
capture_all=True, # Capture all warnings or just specific types
fail_on_unexpected=False, # Fail if unexpected warnings appear
min_count=None, # Minimum number of expected warnings
max_count=None, # Maximum number of expected warnings
):
"""
Decorator to automatically capture and validate warnings in test methods.
Args:
expected_warnings: List of warning messages that MUST be present
forbidden_warnings: List of warning messages that MUST NOT be present
match_type: How to match warnings ("exact", "contains", "regex")
capture_all: Whether to capture all warnings
fail_on_unexpected: Whether to fail if unexpected warnings appear
min_count: Minimum number of warnings expected
max_count: Maximum number of warnings expected
"""
def decorator(test_func):
def wrapper(*args, **kwargs):
# Setup warning capture
with warnings.catch_warnings(record=True) as warning_list:
warnings.simplefilter("always")
# Execute the test function
result = test_func(*args, **kwargs)
# Convert warnings to string messages
captured_messages = [str(w.message) for w in warning_list]
# Validate expected warnings are present
if expected_warnings:
for expected_warning in expected_warnings:
if not _warning_matches(
expected_warning, captured_messages, match_type
):
raise AssertionError(
f"Expected warning '{expected_warning}' not found. "
f"Captured warnings: {captured_messages}"
)
# Validate forbidden warnings are NOT present
if forbidden_warnings:
for forbidden_warning in forbidden_warnings:
if _warning_matches(
forbidden_warning, captured_messages, match_type
):
raise AssertionError(
f"Forbidden warning '{forbidden_warning}' was found. "
f"Captured warnings: {captured_messages}"
)
# Validate warning count constraints
if min_count is not None and len(warning_list) < min_count:
raise AssertionError(
f"Expected at least {min_count} warnings, got {len(warning_list)}"
)
if max_count is not None and len(warning_list) > max_count:
raise AssertionError(
f"Expected at most {max_count} warnings, got {len(warning_list)}"
)
# Validate no unexpected warnings (if enabled)
if fail_on_unexpected and expected_warnings:
all_expected = expected_warnings + (forbidden_warnings or [])
for message in captured_messages:
if not any(
_warning_matches(exp, [message], match_type)
for exp in all_expected
):
raise AssertionError(
f"Unexpected warning found: '{message}'"
)
return result
return wrapper
return decorator
def _warning_matches(pattern, messages, match_type):
"""Helper function to check if pattern matches any message"""
for message in messages:
if match_type == "exact":
if pattern == message:
return True
elif match_type == "contains":
if pattern in message:
return True
elif match_type == "regex":
import re
if re.search(pattern, message):
return True
return False