Skip to content

Commit 4a4c4e8

Browse files
committed
Added failing test for issue #424
tests/test_line_profiler.py::test_nonprofiled_clashing_bytecodes() New test (currently failing) for the case described in issue #424: - One uncalled function passed to the profiler - Another (bytecode-wise) identical function called, but never passed to the profiler - Even profiling is active, the profiler should not have recorded any line event, but it nontheless did
1 parent 07e3776 commit 4a4c4e8

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

tests/test_line_profiler.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
import io
88
import os
99
import pickle
10+
import subprocess
1011
import sys
1112
import textwrap
1213
import types
14+
from pathlib import Path
1315
from tempfile import TemporaryDirectory
1416
import pytest
17+
from ubelt import ChDir
1518
from line_profiler import _line_profiler, LineProfiler, LineStats
1619

1720

@@ -989,6 +992,94 @@ def func(n):
989992
assert entries[-2][1] == 10 + 20
990993

991994

995+
def test_nonprofiled_clashing_bytecodes(tmp_path_factory):
996+
"""
997+
Test that the profiler can distinguish between a profiled function
998+
and a non-profiled one compiling down to the same bytecode.
999+
"""
1000+
# See issue #424
1001+
template = textwrap.dedent("""
1002+
def {}(n): # Any function using this compiles to the same bytecode
1003+
x = 0
1004+
for n in range(1, n + 1):
1005+
x += n
1006+
return x
1007+
""").strip('\n')
1008+
module_name = 'my_module'
1009+
script_name = 'my-script.py'
1010+
outfile = 'out.lprof'
1011+
func_p = 'profiled_func'
1012+
func_no_p = 'nonprofiled_func'
1013+
1014+
# Note: bytecode padding depends on the existence of duplicates,
1015+
# which are counted throughout the lifetime of the `LineProfiler`
1016+
# class. To ensure that we start on a clean slate -- that
1017+
# `LineProfiler` isn't "polluted" by running prior tests -- run the
1018+
# profliing in a subprocess.
1019+
with ChDir(tmp_path_factory.mktemp('test_nonprofiled_clashing_bytecodes')):
1020+
syspath_annex = (Path.cwd() / 'syspath').resolve()
1021+
syspath_annex.mkdir()
1022+
with (syspath_annex / (module_name + '.py')).open('w') as fobj:
1023+
print(
1024+
textwrap.dedent("""
1025+
'''
1026+
This docstring is fluff to make the function definitions overlap in
1027+
line numbers.
1028+
'''
1029+
1030+
1031+
{fp_def}
1032+
""").strip('\n').format(fp_def=template.format(func_p)),
1033+
file=fobj,
1034+
)
1035+
with open(script_name, 'w') as fobj:
1036+
print(
1037+
textwrap.dedent("""
1038+
from line_profiler import LineProfiler
1039+
from {mod} import {fp_name}
1040+
1041+
1042+
{fnp_def}
1043+
1044+
1045+
if __name__ == '__main__':
1046+
prof = LineProfiler()
1047+
prof.add_callable({fp_name})
1048+
with prof:
1049+
# The context turns on profiling for the call, but it
1050+
# shouldn't do anything since the imported and profiled
1051+
# function is not called
1052+
{fnp_name}(10)
1053+
prof.dump_stats({out!r})
1054+
""").strip('\n').format(
1055+
mod=module_name,
1056+
fp_name=func_p,
1057+
fnp_name=func_no_p,
1058+
fnp_def=template.format(func_no_p),
1059+
out=outfile,
1060+
),
1061+
file=fobj,
1062+
)
1063+
1064+
syspath = os.environ.get('PYTHONPATH', '')
1065+
syspath = ('{}:{}' if syspath else '{}').format(syspath_annex, syspath)
1066+
subprocess.run(
1067+
[sys.executable, script_name],
1068+
check=True, env={**os.environ, 'PYTHONPATH': syspath},
1069+
)
1070+
1071+
assert os.path.exists(outfile)
1072+
stats = LineStats.from_files(outfile)
1073+
stats.print() # For debugging purposes
1074+
1075+
# There should only be one function profiled (`profiled_func()`),
1076+
# which however doesn't have any actual data because it was never
1077+
# called
1078+
((*_, func_name), data), = stats.timings.items()
1079+
assert (func_name == func_p), stats
1080+
assert (not data), stats
1081+
1082+
9921083
@pytest.mark.parametrize('force_same_line_numbers', [True, False])
9931084
@pytest.mark.parametrize(
9941085
'ops',

0 commit comments

Comments
 (0)