Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Erotemic committed Aug 3, 2023
1 parent 8e7d012 commit 8d7a2f5
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 14 deletions.
28 changes: 14 additions & 14 deletions line_profiler/line_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,42 +298,42 @@ def show_func(filename, start_lineno, func_name, timings, unit,
}

ALLOW_SCIENTIFIC_NOTATION = 1
col_order = ['line', 'hits', 'time', 'perhit', 'percent']
template = '%6s %9s %12s %8s %8s %-s'

display = {}

# Loop over each line to determine better column formatting.
# Fallback to scientific notation if columns are larger.
# Fallback to scientific notation if columns are larger than a threshold.
for lineno, nhits, time in timings:
if total_time == 0: # Happens rarely on empty function
percent = ''
else:
percent = '%5.1f' % (100 * time / total_time)

time_disp = '%5.1f' % (time * scalar)
if len(time_disp) > default_column_sizes['time'] and ALLOW_SCIENTIFIC_NOTATION:
if ALLOW_SCIENTIFIC_NOTATION and len(time_disp) > default_column_sizes['time']:
time_disp = '%5.1g' % (time * scalar)

perhit_disp = '%5.1f' % (float(time) * scalar / nhits)
if len(perhit_disp) > default_column_sizes['perhit'] and ALLOW_SCIENTIFIC_NOTATION:
if ALLOW_SCIENTIFIC_NOTATION and len(perhit_disp) > default_column_sizes['perhit']:
perhit_disp = '%5.1g' % (float(time) * scalar / nhits)

nhits_disp = "%d" % nhits
if len(nhits_disp) > default_column_sizes['hits'] and ALLOW_SCIENTIFIC_NOTATION:
if ALLOW_SCIENTIFIC_NOTATION and len(nhits_disp) > default_column_sizes['hits']:
nhits_disp = '%g' % nhits

display[lineno] = (nhits_disp, time_disp, perhit_disp, percent)

max_hitlen = max(len(t[0]) for t in display.values())
max_timelen = max(len(t[1]) for t in display.values())
max_perhitlen = max(len(t[2]) for t in display.values())

# Expand column sizes if the numbers are large.
column_sizes = default_column_sizes.copy()
column_sizes['hits'] = max(column_sizes['hits'], max_hitlen)
column_sizes['time'] = max(column_sizes['time'], max_timelen)
column_sizes['perhit'] = max(column_sizes['perhit'], max_perhitlen)
if len(display):
max_hitlen = max(len(t[0]) for t in display.values())
max_timelen = max(len(t[1]) for t in display.values())
max_perhitlen = max(len(t[2]) for t in display.values())
column_sizes['hits'] = max(column_sizes['hits'], max_hitlen)
column_sizes['time'] = max(column_sizes['time'], max_timelen)
column_sizes['perhit'] = max(column_sizes['perhit'], max_perhitlen)

# template = '%6s %9s %12s %8s %8s %-s'
col_order = ['line', 'hits', 'time', 'perhit', 'percent']
template = ' '.join(['%' + str(column_sizes[k]) + 's' for k in col_order])
template = template + ' %-s'

Expand Down
121 changes: 121 additions & 0 deletions tests/complex_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""
A script used in test_complex_case.py
"""
import line_profiler
import atexit


profile = line_profiler.LineProfiler()


@atexit.register
def _show_profile_on_end():
profile.print_stats()


@profile
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()


@profile
def funcy_fib(n):
"""
Alternatite fib function where code splits out over multiple lines
"""
a, b = (
0, 1
)
while a < n:
print(
a, end=' ')
a, b = b, \
a + b
print(
)


@profile
def fib_only_called_by_thread(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()


@profile
def fib_only_called_by_process(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
# FIXME: having two functions with the EXACT same code can cause issues
# a = 'no longer exactly the same'
print()


@profile
def main():
"""
Run a lot of different Fibonacci jobs
"""
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--size', type=int, default=10)
args = parser.parse_args()

size = args.size

for i in range(size):
fib(i)
funcy_fib(
i)
fib(i)

from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=4)
with executor:
jobs = []
for i in range(size):
job = executor.submit(fib, i)
jobs.append(job)

job = executor.submit(funcy_fib, i)
jobs.append(job)

job = executor.submit(fib_only_called_by_thread, i)
jobs.append(job)

for job in jobs:
job.result()

from concurrent.futures import ProcessPoolExecutor
executor = ProcessPoolExecutor(max_workers=4)
with executor:
jobs = []
for i in range(size):
job = executor.submit(fib, i)
jobs.append(job)

job = executor.submit(funcy_fib, i)
jobs.append(job)

job = executor.submit(fib_only_called_by_process, i)
jobs.append(job)

for job in jobs:
job.result()


if __name__ == '__main__':
"""
CommandLine:
cd ~/code/line_profiler/tests/
python complex_example.py --size 10
"""
main()
58 changes: 58 additions & 0 deletions tests/test_complex_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@


def profile_now(func):
"""
Wrap a function to print profile information after it is called.
Args:
func (Callable): function to profile
Returns:
Callable: the wrapped function
"""
import line_profiler
profile = line_profiler.LineProfiler()
new_func = profile(func)

def wraper(*args, **kwargs):
try:
return new_func(*args, **kwargs)
except Exception:
pass
finally:
profile.print_stats(stripzeros=True)

wraper.new_func = new_func
return wraper


def func_to_profile():
list(range(100000))
tuple(range(100000))
set(range(100000))


def test_profile_now():
func = func_to_profile
profile_now(func)()


def test_complex_example():
import sys
import pathlib
import subprocess

try:
test_dpath = pathlib.Path(__file__).parent
except NameError:
# for development
test_dpath = pathlib.Path('~/code/line_profiler/tests').expanduser()

complex_fpath = test_dpath / 'complex_example.py'

proc = subprocess.run([sys.executable, complex_fpath], capture_output=True,
universal_newlines=True)
print(proc.stdout)
print(proc.stderr)
print(proc.returncode)
proc.check_returncode()

0 comments on commit 8d7a2f5

Please sign in to comment.