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

@ -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))