Add normalize flag to remove machine-specific data

This allows for diffing between multiple PySnooper outputs.
This commit is contained in:
Itamar.Raviv 2019-11-19 18:08:54 +02:00 committed by Ram Rachum
parent c0bf4bd006
commit 0c5834196a
6 changed files with 301 additions and 112 deletions

View file

@ -144,6 +144,12 @@ Start all snoop lines with a prefix, to grep for them easily:
@pysnooper.snoop(prefix='ZZZ ')
```
Remove all machine-related data (paths, timestamps, memory addresses) to compare with other traces easily:
```python
@pysnooper.snoop(normalize=True)
```
On multi-threaded apps identify which thread are snooped in output:
```python

View file

@ -22,19 +22,19 @@ if pycompat.PY2:
ipython_filename_pattern = re.compile('^<ipython-input-([0-9]+)-.*>$')
def get_local_reprs(frame, watch=(), custom_repr=(), max_length=None):
def get_local_reprs(frame, watch=(), custom_repr=(), max_length=None, normalize=False):
code = frame.f_code
vars_order = (code.co_varnames + code.co_cellvars + code.co_freevars +
tuple(frame.f_locals.keys()))
result_items = [(key, utils.get_shortish_repr(value, custom_repr,
max_length))
max_length, normalize))
for key, value in frame.f_locals.items()]
result_items.sort(key=lambda key_value: vars_order.index(key_value[0]))
result = collections.OrderedDict(result_items)
for variable in watch:
result.update(sorted(variable.items(frame)))
result.update(sorted(variable.items(frame, normalize)))
return result
@ -201,7 +201,7 @@ class Tracer:
'''
def __init__(self, output=None, watch=(), watch_explode=(), depth=1,
prefix='', overwrite=False, thread_info=False, custom_repr=(),
max_variable_length=100):
max_variable_length=100, normalize=False):
self._write = get_write_function(output, overwrite)
self.watch = [
@ -226,6 +226,7 @@ class Tracer:
self.custom_repr = custom_repr
self.last_source_path = None
self.max_variable_length = max_variable_length
self.normalize = normalize
def __call__(self, function_or_class):
if DISABLED:
@ -351,9 +352,10 @@ class Tracer:
### Finished checking whether we should trace this line. ##############
now = datetime_module.datetime.now().time()
now_string = pycompat.time_isoformat(now, timespec='microseconds')
now_string = pycompat.time_isoformat(now, timespec='microseconds') if not self.normalize else ' ' * 15
line_no = frame.f_lineno
source_path, source = get_path_and_source_from_frame(frame)
source_path = source_path if not self.normalize else os.path.basename(source_path)
if self.last_source_path != source_path:
self.write(u'{indent}Source path:... {source_path}'.
format(**locals()))
@ -361,6 +363,9 @@ class Tracer:
source_line = source[line_no - 1]
thread_info = ""
if self.thread_info:
if self.normalize:
raise NotImplementedError("normalize is not supported with "
"thread_info")
current_thread = threading.current_thread()
thread_info = "{ident}-{name} ".format(
ident=current_thread.ident, name=current_thread.getName())
@ -372,7 +377,9 @@ class Tracer:
self.frame_to_local_reprs[frame] = local_reprs = \
get_local_reprs(frame,
watch=self.watch, custom_repr=self.custom_repr,
max_length=self.max_variable_length)
max_length=self.max_variable_length,
normalize=self.normalize,
)
newish_string = ('Starting var:.. ' if event == 'call' else
'New var:....... ')
@ -437,7 +444,9 @@ class Tracer:
if not ended_by_exception:
return_value_repr = utils.get_shortish_repr(arg,
custom_repr=self.custom_repr,
max_length=self.max_variable_length)
max_length=self.max_variable_length,
normalize=self.normalize,
)
self.write('{indent}Return value:.. {return_value_repr}'.
format(**locals()))

View file

@ -2,6 +2,7 @@
# This program is distributed under the MIT license.
import abc
import re
import sys
from .pycompat import ABC, string_types, collections_abc
@ -55,13 +56,23 @@ def get_repr_function(item, custom_repr):
return repr
def get_shortish_repr(item, custom_repr=(), max_length=None):
DEFAULT_REPR_RE = re.compile(r' at 0x[a-f0-9A-F]{4,}')
def normalize_repr(item_repr):
"""Remove memory address (0x...) from a default python repr"""
return DEFAULT_REPR_RE.sub('', item_repr)
def get_shortish_repr(item, custom_repr=(), max_length=None, normalize=False):
repr_function = get_repr_function(item, custom_repr)
try:
r = repr_function(item)
except Exception:
r = 'REPR FAILED'
r = r.replace('\r', '').replace('\n', '')
if normalize:
r = normalize_repr(r)
if max_length:
r = truncate(r, max_length)
return r

View file

@ -27,15 +27,15 @@ class BaseVariable(pycompat.ABC):
else:
self.unambiguous_source = source
def items(self, frame):
def items(self, frame, normalize=False):
try:
main_value = eval(self.code, frame.f_globals or {}, frame.f_locals)
except Exception:
return ()
return self._items(main_value)
return self._items(main_value, normalize)
@abc.abstractmethod
def _items(self, key):
def _items(self, key, normalize=False):
raise NotImplementedError
@property
@ -51,8 +51,8 @@ class BaseVariable(pycompat.ABC):
class CommonVariable(BaseVariable):
def _items(self, main_value):
result = [(self.source, utils.get_shortish_repr(main_value))]
def _items(self, main_value, normalize=False):
result = [(self.source, utils.get_shortish_repr(main_value, normalize=normalize))]
for key in self._safe_keys(main_value):
try:
if key in self.exclude:
@ -122,7 +122,7 @@ class Indices(Keys):
class Exploding(BaseVariable):
def _items(self, main_value):
def _items(self, main_value, normalize=False):
if isinstance(main_value, Mapping):
cls = Keys
elif isinstance(main_value, Sequence):
@ -130,4 +130,4 @@ class Exploding(BaseVariable):
else:
cls = Attrs
return cls(self.source, self.exclude)._items(main_value)
return cls(self.source, self.exclude)._items(main_value, normalize)

View file

@ -159,13 +159,14 @@ def test_multi_thread_info():
)
def test_callable():
@pytest.mark.parametrize("normalize", (True, False))
def test_callable(normalize):
string_io = io.StringIO()
def write(msg):
string_io.write(msg)
@pysnooper.snoop(write)
@pysnooper.snoop(write, normalize=normalize)
def my_function(foo):
x = 7
y = 8
@ -187,13 +188,13 @@ def test_callable():
LineEntry('return y + x'),
ReturnEntry('return y + x'),
ReturnValueEntry('15'),
)
),
normalize=normalize,
)
def test_watch():
@pytest.mark.parametrize("normalize", (True, False))
def test_watch(normalize):
class Foo(object):
def __init__(self):
self.x = 2
@ -205,7 +206,7 @@ def test_watch():
'foo.x',
'io.__name__',
'len(foo.__dict__["x"] * "abc")',
))
), normalize=normalize)
def my_function():
foo = Foo()
for i in range(2):
@ -240,18 +241,19 @@ def test_watch():
LineEntry(),
ReturnEntry(),
ReturnValueEntry('None')
)
),
normalize=normalize,
)
def test_watch_explode():
@pytest.mark.parametrize("normalize", (True, False))
def test_watch_explode(normalize):
class Foo:
def __init__(self, x, y):
self.x = x
self.y = y
@pysnooper.snoop(watch_explode=('_d', '_point', 'lst + []'))
@pysnooper.snoop(watch_explode=('_d', '_point', 'lst + []'), normalize=normalize)
def my_function():
_d = {'a': 1, 'b': 2, 'c': 'ignore'}
_point = Foo(x=3, y=4)
@ -290,11 +292,13 @@ def test_watch_explode():
VariableEntry('lst + []'),
ReturnEntry(),
ReturnValueEntry('None')
)
),
normalize=normalize,
)
def test_variables_classes():
@pytest.mark.parametrize("normalize", (True, False))
def test_variables_classes(normalize):
class WithSlots(object):
__slots__ = ('x', 'y')
@ -307,7 +311,7 @@ def test_variables_classes():
pysnooper.Attrs('_d'), # doesn't have attributes
pysnooper.Attrs('_s'),
pysnooper.Indices('_lst')[-3:],
))
), normalize=normalize)
def my_function():
_d = {'a': 1, 'b': 2, 'c': 'ignore'}
_s = WithSlots()
@ -339,13 +343,13 @@ def test_variables_classes():
VariableEntry('_lst[999]', '999'),
ReturnEntry(),
ReturnValueEntry('None')
)
),
normalize=normalize,
)
def test_single_watch_no_comma():
@pytest.mark.parametrize("normalize", (True, False))
def test_single_watch_no_comma(normalize):
class Foo(object):
def __init__(self):
self.x = 2
@ -353,7 +357,7 @@ def test_single_watch_no_comma():
def square(self):
self.x **= 2
@pysnooper.snoop(watch='foo')
@pysnooper.snoop(watch='foo', normalize=normalize)
def my_function():
foo = Foo()
for i in range(2):
@ -381,12 +385,14 @@ def test_single_watch_no_comma():
LineEntry(),
ReturnEntry(),
ReturnValueEntry('None')
)
),
normalize=normalize,
)
def test_long_variable():
@pysnooper.snoop()
@pytest.mark.parametrize("normalize", (True, False))
def test_long_variable(normalize):
@pysnooper.snoop(normalize=normalize)
def my_function():
foo = list(range(1000))
return foo
@ -407,13 +413,14 @@ def test_long_variable():
LineEntry(),
ReturnEntry(),
ReturnValueEntry(value_regex=regex)
)
),
normalize=normalize,
)
def test_long_variable_with_custom_max_variable_length():
@pysnooper.snoop(max_variable_length=200)
@pytest.mark.parametrize("normalize", (True, False))
def test_long_variable_with_custom_max_variable_length(normalize):
@pysnooper.snoop(max_variable_length=200, normalize=normalize)
def my_function():
foo = list(range(1000))
return foo
@ -434,12 +441,14 @@ def test_long_variable_with_custom_max_variable_length():
LineEntry(),
ReturnEntry(),
ReturnValueEntry(value_regex=regex)
)
),
normalize=normalize,
)
def test_long_variable_with_infinite_max_variable_length():
@pysnooper.snoop(max_variable_length=None)
@pytest.mark.parametrize("normalize", (True, False))
def test_long_variable_with_infinite_max_variable_length(normalize):
@pysnooper.snoop(max_variable_length=None, normalize=normalize)
def my_function():
foo = list(range(1000))
return foo
@ -460,17 +469,18 @@ def test_long_variable_with_infinite_max_variable_length():
LineEntry(),
ReturnEntry(),
ReturnValueEntry(value_regex=regex)
)
),
normalize=normalize,
)
def test_repr_exception():
@pytest.mark.parametrize("normalize", (True, False))
def test_repr_exception(normalize):
class Bad(object):
def __repr__(self):
1 / 0
@pysnooper.snoop()
@pysnooper.snoop(normalize=normalize)
def my_function():
bad = Bad()
@ -489,11 +499,13 @@ def test_repr_exception():
VariableEntry('bad', value='REPR FAILED'),
ReturnEntry(),
ReturnValueEntry('None')
)
),
normalize=normalize,
)
def test_depth():
@pytest.mark.parametrize("normalize", (True, False))
def test_depth(normalize):
string_io = io.StringIO()
def f4(x4):
@ -508,7 +520,7 @@ def test_depth():
result2 = f3(x2)
return result2
@pysnooper.snoop(string_io, depth=3)
@pysnooper.snoop(string_io, depth=3, normalize=normalize)
def f1(x1):
result1 = f2(x1)
return result1
@ -549,16 +561,18 @@ def test_depth():
LineEntry(),
ReturnEntry(),
ReturnValueEntry('20'),
)
),
normalize=normalize,
)
def test_method_and_prefix():
@pytest.mark.parametrize("normalize", (True, False))
def test_method_and_prefix(normalize):
class Baz(object):
def __init__(self):
self.x = 2
@pysnooper.snoop(watch=('self.x',), prefix='ZZZ')
@pysnooper.snoop(watch=('self.x',), prefix='ZZZ', normalize=normalize)
def square(self):
foo = 7
self.x **= 2
@ -587,15 +601,17 @@ def test_method_and_prefix():
ReturnEntry(prefix='ZZZ'),
ReturnValueEntry(prefix='ZZZ'),
),
prefix='ZZZ'
prefix='ZZZ',
normalize=normalize,
)
def test_file_output():
@pytest.mark.parametrize("normalize", (True, False))
def test_file_output(normalize):
with mini_toolbox.create_temp_folder(prefix='pysnooper') as folder:
path = folder / 'foo.log'
@pysnooper.snoop(path)
@pysnooper.snoop(path, normalize=normalize)
def my_function(_foo):
x = 7
y = 8
@ -618,18 +634,20 @@ def test_file_output():
LineEntry('return y + x'),
ReturnEntry('return y + x'),
ReturnValueEntry('15'),
)
),
normalize=normalize,
)
def test_confusing_decorator_lines():
@pytest.mark.parametrize("normalize", (True, False))
def test_confusing_decorator_lines(normalize):
string_io = io.StringIO()
def empty_decorator(function):
return function
@empty_decorator
@pysnooper.snoop(string_io,
@pysnooper.snoop(string_io, normalize=normalize,
depth=2) # Multi-line decorator for extra confusion!
@empty_decorator
@empty_decorator
@ -661,13 +679,15 @@ def test_confusing_decorator_lines():
# back in my_function
ReturnEntry(),
ReturnValueEntry('15'),
)
),
normalize=normalize,
)
def test_lambda():
@pytest.mark.parametrize("normalize", (True, False))
def test_lambda(normalize):
string_io = io.StringIO()
my_function = pysnooper.snoop(string_io)(lambda x: x ** 2)
my_function = pysnooper.snoop(string_io, normalize=normalize)(lambda x: x ** 2)
result = my_function(7)
assert result == 49
output = string_io.getvalue()
@ -680,7 +700,8 @@ def test_lambda():
LineEntry(source_regex='^my_function = pysnooper.*'),
ReturnEntry(source_regex='^my_function = pysnooper.*'),
ReturnValueEntry('49'),
)
),
normalize=normalize,
)
@ -820,9 +841,10 @@ def test_needs_parentheses():
assert needs_parentheses('x if z else y')
def test_with_block():
@pytest.mark.parametrize("normalize", (True, False))
def test_with_block(normalize):
# Testing that a single Tracer can handle many mixed uses
snoop = pysnooper.snoop()
snoop = pysnooper.snoop(normalize=normalize)
def foo(x):
if x == 0:
@ -940,10 +962,12 @@ def test_with_block():
ReturnEntry('qux()'),
ReturnValueEntry('None'),
),
normalize=normalize,
)
def test_with_block_depth():
@pytest.mark.parametrize("normalize", (True, False))
def test_with_block_depth(normalize):
string_io = io.StringIO()
def f4(x4):
@ -960,7 +984,7 @@ def test_with_block_depth():
def f1(x1):
str(3)
with pysnooper.snoop(string_io, depth=3):
with pysnooper.snoop(string_io, depth=3, normalize=normalize):
result1 = f2(x1)
return result1
@ -974,6 +998,7 @@ def test_with_block_depth():
VariableEntry(),
VariableEntry(),
VariableEntry(),
VariableEntry(),
LineEntry('result1 = f2(x1)'),
VariableEntry(),
@ -995,10 +1020,13 @@ def test_with_block_depth():
LineEntry(),
ReturnEntry(),
ReturnValueEntry('20'),
)
),
normalize=normalize,
)
def test_cellvars():
@pytest.mark.parametrize("normalize", (True, False))
def test_cellvars(normalize):
string_io = io.StringIO()
def f2(a):
@ -1012,7 +1040,7 @@ def test_cellvars():
return f3(a)
def f1(a):
with pysnooper.snoop(string_io, depth=4):
with pysnooper.snoop(string_io, depth=4, normalize=normalize):
result1 = f2(a)
return result1
@ -1026,6 +1054,7 @@ def test_cellvars():
VariableEntry(),
VariableEntry(),
VariableEntry(),
VariableEntry(),
LineEntry('result1 = f2(a)'),
VariableEntry(),
@ -1057,10 +1086,13 @@ def test_cellvars():
ReturnValueEntry(),
ReturnEntry(),
ReturnValueEntry(),
)
),
normalize=normalize,
)
def test_var_order():
@pytest.mark.parametrize("normalize", (True, False))
def test_var_order(normalize):
string_io = io.StringIO()
def f(one, two, three, four):
@ -1070,7 +1102,7 @@ def test_var_order():
five, six, seven = 5, 6, 7
with pysnooper.snoop(string_io, depth=2):
with pysnooper.snoop(string_io, depth=2, normalize=normalize):
result = f(1, 2, 3, 4)
output = string_io.getvalue()
@ -1080,6 +1112,7 @@ def test_var_order():
SourcePathEntry(),
VariableEntry(),
VariableEntry(),
VariableEntry(),
LineEntry('result = f(1, 2, 3, 4)'),
VariableEntry("one", "1"),
@ -1100,7 +1133,8 @@ def test_var_order():
VariableEntry("seven", "7"),
ReturnEntry(),
ReturnValueEntry(),
)
),
normalize=normalize,
)
@ -1209,7 +1243,8 @@ def test_generator():
)
def test_custom_repr():
@pytest.mark.parametrize("normalize", (True, False))
def test_custom_repr(normalize):
string_io = io.StringIO()
def large(l):
@ -1227,7 +1262,8 @@ def test_custom_repr():
@pysnooper.snoop(string_io, custom_repr=(
(large, print_list_size),
(dict, print_dict),
(evil_condition, lambda x: 'I am evil')))
(evil_condition, lambda x: 'I am evil')),
normalize=normalize,)
def sum_to_x(x):
l = list(range(x))
a = {'1': 1, '2': 2}
@ -1249,13 +1285,16 @@ def test_custom_repr():
LineEntry(),
ReturnEntry(),
ReturnValueEntry('49995000'),
)
),
normalize=normalize,
)
def test_custom_repr_single():
@pytest.mark.parametrize("normalize", (True, False))
def test_custom_repr_single(normalize):
string_io = io.StringIO()
@pysnooper.snoop(string_io, custom_repr=(list, lambda l: 'foofoo!'))
@pysnooper.snoop(string_io, custom_repr=(list, lambda l: 'foofoo!'), normalize=normalize)
def sum_to_x(x):
l = list(range(x))
return 7
@ -1274,7 +1313,8 @@ def test_custom_repr_single():
LineEntry(),
ReturnEntry(),
ReturnValueEntry('7'),
)
),
normalize=normalize,
)
@ -1297,10 +1337,11 @@ def test_disable():
assert not output
def test_class():
@pytest.mark.parametrize("normalize", (True, False))
def test_class(normalize):
string_io = io.StringIO()
@pysnooper.snoop(string_io)
@pysnooper.snoop(string_io, normalize=normalize)
class MyClass(object):
def __init__(self):
self.x = 7
@ -1317,12 +1358,12 @@ def test_class():
output,
(
SourcePathEntry(),
VariableEntry('self', value_regex="u?.+MyClass object at"),
VariableEntry('self', value_regex="u?.+MyClass object"),
CallEntry('def __init__(self):'),
LineEntry('self.x = 7'),
ReturnEntry('self.x = 7'),
ReturnValueEntry('None'),
VariableEntry('self', value_regex="u?.+MyClass object at"),
VariableEntry('self', value_regex="u?.+MyClass object"),
VariableEntry('foo', value_regex="u?'baba'"),
CallEntry('def my_method(self, foo):'),
LineEntry('y = 8'),
@ -1330,10 +1371,13 @@ def test_class():
LineEntry('return y + self.x'),
ReturnEntry('return y + self.x'),
ReturnValueEntry('15'),
)
),
normalize=normalize,
)
def test_class_with_decorated_method():
@pytest.mark.parametrize("normalize", (True, False))
def test_class_with_decorated_method(normalize):
string_io = io.StringIO()
def decorator(function):
@ -1342,7 +1386,7 @@ def test_class_with_decorated_method():
return result
return wrapper
@pysnooper.snoop(string_io)
@pysnooper.snoop(string_io, normalize=normalize)
class MyClass(object):
def __init__(self):
self.x = 7
@ -1360,25 +1404,27 @@ def test_class_with_decorated_method():
output,
(
SourcePathEntry(),
VariableEntry('self', value_regex="u?.+MyClass object at"),
VariableEntry('self', value_regex="u?.+MyClass object"),
CallEntry('def __init__(self):'),
LineEntry('self.x = 7'),
ReturnEntry('self.x = 7'),
ReturnValueEntry('None'),
VariableEntry('args', value_regex=r"\(<.+>, 'baba'\)"),
VariableEntry('kwargs', value_regex=r"\{\}"),
VariableEntry('function', value_regex="u?.+my_method at"),
VariableEntry('function', value_regex="u?.+my_method"),
CallEntry('def wrapper(*args, **kwargs):'),
LineEntry('result = function(*args, **kwargs)'),
VariableEntry('result', '15'),
LineEntry('return result'),
ReturnEntry('return result'),
ReturnValueEntry('15'),
)
),
normalize=normalize,
)
def test_class_with_decorated_method_and_snoop_applied_to_method():
@pytest.mark.parametrize("normalize", (True, False))
def test_class_with_decorated_method_and_snoop_applied_to_method(normalize):
string_io = io.StringIO()
def decorator(function):
@ -1387,13 +1433,13 @@ def test_class_with_decorated_method_and_snoop_applied_to_method():
return result
return wrapper
@pysnooper.snoop(string_io)
@pysnooper.snoop(string_io, normalize=normalize)
class MyClass(object):
def __init__(self):
self.x = 7
@decorator
@pysnooper.snoop(string_io)
@pysnooper.snoop(string_io, normalize=normalize)
def my_method(self, foo):
y = 8
return y + self.x
@ -1406,18 +1452,18 @@ def test_class_with_decorated_method_and_snoop_applied_to_method():
output,
(
SourcePathEntry(),
VariableEntry('self', value_regex="u?.*MyClass object at"),
VariableEntry('self', value_regex="u?.*MyClass object"),
CallEntry('def __init__(self):'),
LineEntry('self.x = 7'),
ReturnEntry('self.x = 7'),
ReturnValueEntry('None'),
VariableEntry('args', value_regex=r"u?\(<.+>, 'baba'\)"),
VariableEntry('kwargs', value_regex=r"u?\{\}"),
VariableEntry('function', value_regex="u?.*my_method at"),
VariableEntry('function', value_regex="u?.*my_method"),
CallEntry('def wrapper(*args, **kwargs):'),
LineEntry('result = function(*args, **kwargs)'),
SourcePathEntry(),
VariableEntry('self', value_regex="u?.*MyClass object at"),
VariableEntry('self', value_regex="u?.*MyClass object"),
VariableEntry('foo', value_regex="u?'baba'"),
CallEntry('def my_method(self, foo):'),
LineEntry('y = 8'),
@ -1429,14 +1475,16 @@ def test_class_with_decorated_method_and_snoop_applied_to_method():
LineEntry('return result'),
ReturnEntry('return result'),
ReturnValueEntry('15'),
)
),
normalize=normalize,
)
def test_class_with_property():
@pytest.mark.parametrize("normalize", (True, False))
def test_class_with_property(normalize):
string_io = io.StringIO()
@pysnooper.snoop(string_io)
@pysnooper.snoop(string_io, normalize=normalize)
class MyClass(object):
def __init__(self):
self._x = 0
@ -1478,37 +1526,39 @@ def test_class_with_property():
output,
(
SourcePathEntry(),
VariableEntry('self', value_regex="u?.*MyClass object at"),
VariableEntry('self', value_regex="u?.*MyClass object"),
CallEntry('def __init__(self):'),
LineEntry('self._x = 0'),
ReturnEntry('self._x = 0'),
ReturnValueEntry('None'),
# Called from getter
VariableEntry('self', value_regex="u?.*MyClass object at"),
VariableEntry('self', value_regex="u?.*MyClass object"),
CallEntry('def plain_method(self):'),
LineEntry('pass'),
ReturnEntry('pass'),
ReturnValueEntry('None'),
# Called from setter
VariableEntry('self', value_regex="u?.*MyClass object at"),
VariableEntry('self', value_regex="u?.*MyClass object"),
CallEntry('def plain_method(self):'),
LineEntry('pass'),
ReturnEntry('pass'),
ReturnValueEntry('None'),
# Called from deleter
VariableEntry('self', value_regex="u?.*MyClass object at"),
VariableEntry('self', value_regex="u?.*MyClass object"),
CallEntry('def plain_method(self):'),
LineEntry('pass'),
ReturnEntry('pass'),
ReturnValueEntry('None'),
)
),
normalize=normalize,
)
def test_snooping_on_class_does_not_cause_base_class_to_be_snooped():
@pytest.mark.parametrize("normalize", (True, False))
def test_snooping_on_class_does_not_cause_base_class_to_be_snooped(normalize):
string_io = io.StringIO()
class UnsnoopedBaseClass(object):
@ -1518,7 +1568,7 @@ def test_snooping_on_class_does_not_cause_base_class_to_be_snooped():
def method_on_base_class(self):
self.method_on_base_class_was_called = True
@pysnooper.snoop(string_io)
@pysnooper.snoop(string_io, normalize=normalize)
class MyClass(UnsnoopedBaseClass):
def method_on_child_class(self):
self.method_on_base_class()
@ -1534,10 +1584,100 @@ def test_snooping_on_class_does_not_cause_base_class_to_be_snooped():
output,
(
SourcePathEntry(),
VariableEntry('self', value_regex="u?.*MyClass object at"),
VariableEntry('self', value_regex="u?.*MyClass object"),
CallEntry('def method_on_child_class(self):'),
LineEntry('self.method_on_base_class()'),
ReturnEntry('self.method_on_base_class()'),
ReturnValueEntry('None'),
)
),
normalize=normalize,
)
def test_normalize():
string_io = io.StringIO()
class A:
def __init__(self, a):
self.a = a
@pysnooper.snoop(string_io, normalize=True)
def add():
a = A(19)
b = A(22)
res = a.a + b.a
return res
add()
output = string_io.getvalue()
assert_output(
output,
(
SourcePathEntry('test_pysnooper.py'),
VariableEntry('A', value_regex=r"<class .*\.A.?>"),
CallEntry('def add():'),
LineEntry('a = A(19)'),
VariableEntry('a', value_regex=r"<.*\.A (?:object|instance)>"),
LineEntry('b = A(22)'),
VariableEntry('b', value_regex=r"<.*\.A (?:object|instance)>"),
LineEntry('res = a.a + b.a'),
VariableEntry('res', value="41"),
LineEntry('return res'),
ReturnEntry('return res'),
ReturnValueEntry('41'),
)
)
def test_normalize_prefix():
string_io = io.StringIO()
_prefix = 'ZZZZ'
class A:
def __init__(self, a):
self.a = a
@pysnooper.snoop(string_io, normalize=True, prefix=_prefix)
def add():
a = A(19)
b = A(22)
res = a.a + b.a
return res
add()
output = string_io.getvalue()
assert_output(
output,
(
SourcePathEntry('test_pysnooper.py', prefix=_prefix),
VariableEntry('A', value_regex=r"<class .*\.A.?>", prefix=_prefix),
CallEntry('def add():', prefix=_prefix),
LineEntry('a = A(19)', prefix=_prefix),
VariableEntry('a', value_regex=r"<.*\.A (?:object|instance)>", prefix=_prefix),
LineEntry('b = A(22)', prefix=_prefix),
VariableEntry('b', value_regex=r"<.*\.A (?:object|instance)>", prefix=_prefix),
LineEntry('res = a.a + b.a', prefix=_prefix),
VariableEntry('res', value="41", prefix=_prefix),
LineEntry('return res', prefix=_prefix),
ReturnEntry('return res', prefix=_prefix),
ReturnValueEntry('41', prefix=_prefix),
)
)
def test_normalize_thread_info():
string_io = io.StringIO()
class A:
def __init__(self, a):
self.a = a
@pysnooper.snoop(string_io, normalize=True, thread_info=True)
def add():
a = A(19)
b = A(22)
res = a.a + b.a
return res
with pytest.raises(NotImplementedError):
add()

View file

@ -1,10 +1,12 @@
# Copyright 2019 Ram Rachum and collaborators.
# This program is distributed under the MIT license.
import os
import re
import abc
import inspect
from pysnooper.utils import DEFAULT_REPR_RE
try:
from itertools import zip_longest
except ImportError:
@ -202,7 +204,7 @@ class _BaseEventEntry(_BaseEntry):
if source is not None:
assert source_regex is None
self.line_pattern = re.compile(
r"""^%s(?P<indent>(?: {4})*)[0-9:.]{15} """
r"""^%s(?P<indent>(?: {4})*)(?:(?:[0-9:.]{15})|(?: {15})) """
r"""(?P<thread_info>[0-9]+-[0-9A-Za-z_-]+[ ]+)?"""
r"""(?P<event_name>[a-z_]*) +(?P<line_number>[0-9]*) """
r"""+(?P<source>.*)$""" % (re.escape(self.prefix,))
@ -269,7 +271,25 @@ class OutputFailure(Exception):
pass
def assert_output(output, expected_entries, prefix=None):
def verify_normalize(lines, prefix):
time_re = re.compile(r"[0-9:.]{15}")
src_re = re.compile(r'^(?: *)Source path:\.\.\. (.*)$')
for line in lines:
if DEFAULT_REPR_RE.search(line):
msg = "normalize is active, memory address should not appear"
raise OutputFailure(line, msg)
no_prefix = line.replace(prefix if prefix else '', '').strip()
if time_re.match(no_prefix):
msg = "normalize is active, time should not appear"
raise OutputFailure(line, msg)
m = src_re.match(line)
if m:
if not os.path.basename(m.group(1)) == m.group(1):
msg = "normalize is active, path should be only basename"
raise OutputFailure(line, msg)
def assert_output(output, expected_entries, prefix=None, normalize=False):
lines = tuple(filter(None, output.split('\n')))
if prefix is not None:
@ -277,6 +297,9 @@ def assert_output(output, expected_entries, prefix=None):
if not line.startswith(prefix):
raise OutputFailure(line)
if normalize:
verify_normalize(lines, prefix)
any_mismatch = False
result = ''
template = u'\n{line!s:%s} {expected_entry} {arrow}' % max(map(len, lines))