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
|
# Copyright (C) 2010 Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of Vinay Sajip
# not be used in advertising or publicity pertaining to distribution
# of the software without specific, written prior permission.
# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import logging
from logging.handlers import BufferingHandler
class TestHandler(BufferingHandler):
"""
This handler collects records in a buffer for later inspection by
your unit test code.
:param matcher: The :class:`~logutils.testing.Matcher` instance to
use for matching.
"""
def __init__(self, matcher):
# BufferingHandler takes a "capacity" argument
# so as to know when to flush. As we're overriding
# shouldFlush anyway, we can set a capacity of zero.
# You can call flush() manually to clear out the
# buffer.
BufferingHandler.__init__(self, 0)
self.formatted = []
self.matcher = matcher
def shouldFlush(self):
"""
Should the buffer be flushed?
This returns `False` - you'll need to flush manually, usually after
your unit test code checks the buffer contents against your
expectations.
"""
return False
def emit(self, record):
"""
Saves the `__dict__` of the record in the `buffer` attribute,
and the formatted records in the `formatted` attribute.
:param record: The record to emit.
"""
self.formatted.append(self.format(record))
self.buffer.append(record.__dict__)
def flush(self):
"""
Clears out the `buffer` and `formatted` attributes.
"""
BufferingHandler.flush(self)
self.formatted = []
def matches(self, **kwargs):
"""
Look for a saved dict whose keys/values match the supplied arguments.
Return `True` if found, else `False`.
:param kwargs: A set of keyword arguments whose names are LogRecord
attributes and whose values are what you want to
match in a stored LogRecord.
"""
result = False
for d in self.buffer:
if self.matcher.matches(d, **kwargs):
result = True
break
#if not result:
# print('*** matcher failed completely on %d records' % len(self.buffer))
return result
def matchall(self, kwarglist):
"""
Accept a list of keyword argument values and ensure that the handler's
buffer of stored records matches the list one-for-one.
Return `True` if exactly matched, else `False`.
:param kwarglist: A list of keyword-argument dictionaries, each of
which will be passed to :meth:`matches` with the
corresponding record from the buffer.
"""
if self.count != len(kwarglist):
result = False
else:
result = True
for d, kwargs in zip(self.buffer, kwarglist):
if not self.matcher.matches(d, **kwargs):
result = False
break
return result
@property
def count(self):
"""
The number of records in the buffer.
"""
return len(self.buffer)
class Matcher(object):
"""
This utility class matches a stored dictionary of
:class:`logging.LogRecord` attributes with keyword arguments
passed to its :meth:`~logutils.testing.Matcher.matches` method.
"""
_partial_matches = ('msg', 'message')
"""
A list of :class:`logging.LogRecord` attribute names which
will be checked for partial matches. If not in this list,
an exact match will be attempted.
"""
def matches(self, d, **kwargs):
"""
Try to match a single dict with the supplied arguments.
Keys whose values are strings and which are in self._partial_matches
will be checked for partial (i.e. substring) matches. You can extend
this scheme to (for example) do regular expression matching, etc.
Return `True` if found, else `False`.
:param kwargs: A set of keyword arguments whose names are LogRecord
attributes and whose values are what you want to
match in a stored LogRecord.
"""
result = True
for k in kwargs:
v = kwargs[k]
dv = d.get(k)
if not self.match_value(k, dv, v):
#print('*** matcher failed: %s, %r, %r' % (k, dv, v))
result = False
break
return result
def match_value(self, k, dv, v):
"""
Try to match a single stored value (dv) with a supplied value (v).
Return `True` if found, else `False`.
:param k: The key value (LogRecord attribute name).
:param dv: The stored value to match against.
:param v: The value to compare with the stored value.
"""
if type(v) != type(dv):
result = False
elif type(dv) is not str or k not in self._partial_matches:
result = (v == dv)
else:
result = dv.find(v) >= 0
#if not result:
# print('*** matcher failed on %s: %r vs. %r' % (k, dv, v))
return result
|