Skip to content

Commit d9f20cc

Browse files
committed
Added tests
tests/test_line_profiler.py test_multiple_profilers_metadata() Test that the metadata wrappers on `line_profiler.LineProfiler` correctly wraps around the corresponding attributes/descriptors on `line_profiler._line_profiler.LineProfiler` (the Cython-level object) test_multiple_profilers_usage() Test that multiple instances of `line_profiler.LineProfiler` can function simultaneously and separately
1 parent 1116e15 commit d9f20cc

1 file changed

Lines changed: 155 additions & 0 deletions

File tree

tests/test_line_profiler.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,3 +623,158 @@ def test_profile_generated_code():
623623

624624
# .. as well as the generated code name
625625
assert generated_code_name in output
626+
627+
628+
def test_multiple_profilers_metadata():
629+
"""
630+
Test the curation of profiler metadata (e.g. `.code_hash_map`,
631+
`.dupes_map`, `.code_map`) from the underlying C-level profiler.
632+
"""
633+
from copy import deepcopy
634+
from operator import attrgetter
635+
from warnings import warn
636+
637+
prof1 = LineProfiler()
638+
prof2 = LineProfiler()
639+
cprof = prof1._get_c_profiler()
640+
assert prof2._get_c_profiler() is cprof
641+
642+
@prof1
643+
@prof2
644+
def f(c=False):
645+
get_time = attrgetter('c_last_time' if c else 'last_time')
646+
t1 = get_time(prof1)
647+
t2 = get_time(prof2)
648+
return [t1, t2, get_time(cprof)]
649+
650+
@prof1
651+
def g():
652+
return [prof1.enable_count, prof2.enable_count]
653+
654+
@prof2
655+
def h(): # Same bytecode as `g()`
656+
return [prof1.enable_count, prof2.enable_count]
657+
658+
get_code = attrgetter('__wrapped__.__code__')
659+
660+
# `.functions`
661+
assert prof1.functions == [f.__wrapped__, g.__wrapped__]
662+
assert prof2.functions == [f.__wrapped__, h.__wrapped__]
663+
# `.enable_count`
664+
# (Note: `.enable_count` is automatically in-/de-cremented in
665+
# decorated functions, so we need to access it within a called
666+
# function)
667+
assert g() == [1, 0]
668+
assert h() == [0, 1]
669+
assert prof1.enable_count == prof2.enable_count == cprof.enable_count == 0
670+
# `.timer_unit`
671+
assert prof1.timer_unit == prof2.timer_unit == cprof.timer_unit
672+
# `.code_hash_map`
673+
assert set(prof1.code_hash_map) == {get_code(f), get_code(g)}
674+
assert set(prof2.code_hash_map) == {get_code(f), get_code(h)}
675+
676+
# `.c_code_map`
677+
prof1_line_hashes = {h for hashes in prof1.code_hash_map.values()
678+
for h in hashes}
679+
assert set(prof1.c_code_map) == prof1_line_hashes
680+
prof2_line_hashes = {h for hashes in prof2.code_hash_map.values()
681+
for h in hashes}
682+
assert set(prof2.c_code_map) == prof2_line_hashes
683+
# `.code_map`
684+
assert set(prof1.code_map) == {get_code(f), get_code(g)}
685+
assert len(prof1.code_map[get_code(f)]) == 0
686+
assert len(prof1.code_map[get_code(g)]) == 1
687+
assert set(prof2.code_map) == {get_code(f), get_code(h)}
688+
assert len(prof2.code_map[get_code(f)]) == 0
689+
assert len(prof2.code_map[get_code(h)]) == 1
690+
t1, t2, _ = f() # Timing info gathered after calling the function
691+
assert len(prof1.code_map[get_code(f)]) == 4 # 4 real lines
692+
assert len(prof2.code_map[get_code(f)]) == 4
693+
694+
# `.c_last_time`
695+
# (Note: `.c_last_time` is transient, so we need to access it within
696+
# a called function)
697+
ct1, ct2, _ = f(c=True)
698+
assert set(ct1) == set(ct2) == {hash(get_code(f).co_code)}
699+
# `.last_time`
700+
# (Note: `.last_time` is currently bugged; since `.c_last_time`
701+
# stores code-block hashes and `.code_hash_map` line hashes,
702+
# `line_profiler._line_profiler.LineProfiler.last_time` never gets a
703+
# hash match and is thus always empty)
704+
t1, t2, tc = f(c=False)
705+
if tc:
706+
expected = {get_code(f)}
707+
else:
708+
msg = ('`line_profiler/_line_profiler.pyx::LineProfiler.last_time` '
709+
'is always empty because `.c_last_time` and `.code_hash_map` '
710+
'use different types of hashes (see PR #344)')
711+
warn(msg, DeprecationWarning)
712+
expected = set()
713+
assert set(t1) == set(t2) == set(tc) == expected
714+
715+
# `.dupes_map` (introduce a dupe for this)
716+
# Note: `h.__wrapped__.__code__` is padded but the `.dupes_map`
717+
# entries are not
718+
assert prof1.dupes_map == {get_code(f).co_code: [get_code(f)],
719+
get_code(g).co_code: [get_code(g)]}
720+
h = prof1(h)
721+
dupes = deepcopy(prof1.dupes_map)
722+
h_code = dupes[get_code(g).co_code][-1]
723+
assert get_code(h).co_code.startswith(h_code.co_code)
724+
dupes[get_code(g).co_code][-1] = (h_code
725+
.replace(co_code=get_code(h).co_code))
726+
assert dupes == {get_code(f).co_code: [get_code(f)],
727+
get_code(g).co_code: [get_code(g), get_code(h)]}
728+
729+
730+
def test_multiple_profilers_usage():
731+
"""
732+
Test using more than one profilers simultaneously.
733+
"""
734+
prof1 = LineProfiler()
735+
prof2 = LineProfiler()
736+
737+
def sum_n(n):
738+
x = 0
739+
for n in range(1, n + 1):
740+
x += n
741+
return x
742+
743+
@prof1
744+
def sum_n_sq(n):
745+
x = 0
746+
for n in range(1, n + 1):
747+
x += n ** 2
748+
return x
749+
750+
@prof2
751+
def sum_n_cb(n):
752+
x = 0
753+
for n in range(1, n + 1):
754+
x += n ** 3
755+
return x
756+
757+
# If we decorate a wrapper, just "register" the profiler with the
758+
# existing wrapper and add the wrapped function
759+
sum_n_wrapper = prof1(sum_n)
760+
assert prof1.functions == [sum_n_sq.__wrapped__, sum_n]
761+
sum_n_wrapper_2 = prof2(sum_n_wrapper)
762+
assert prof2.functions == [sum_n_cb.__wrapped__, sum_n]
763+
assert sum_n_wrapper_2 is sum_n_wrapper
764+
765+
# Call the functions
766+
n = 400
767+
assert sum_n_wrapper(n) == .5 * n * (n + 1)
768+
assert 6 * sum_n_sq(n) == n * (n + 1) * (2 * n + 1)
769+
assert sum_n_cb(n) == .25 * (n * (n + 1)) ** 2
770+
771+
# Inspect the timings
772+
t1 = {fname: entries
773+
for (*_, fname), entries in prof1.get_stats().timings.items()}
774+
t2 = {fname: entries
775+
for (*_, fname), entries in prof2.get_stats().timings.items()}
776+
assert set(t1) == {'sum_n_sq', 'sum_n'}
777+
assert set(t2) == {'sum_n_cb', 'sum_n'}
778+
assert t1['sum_n'][2][1] == t2['sum_n'][2][1] == n
779+
assert t1['sum_n_sq'][2][1] == n
780+
assert t2['sum_n_cb'][2][1] == n

0 commit comments

Comments
 (0)