Skip to content

Commit

Permalink
Add test for pymupdf#2791 - memory leak in Document.save().
Browse files Browse the repository at this point in the history
  • Loading branch information
julian-smith-artifex-com committed Nov 14, 2023
1 parent 82a8cb8 commit 2c91156
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 0 deletions.
Binary file added tests/resources/test_2791_content.pdf
Binary file not shown.
Binary file added tests/resources/test_2791_coverpage.pdf
Binary file not shown.
87 changes: 87 additions & 0 deletions tests/test_2791.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import fitz

import gc
import os
import platform
import sys


def merge_pdf(content: bytes, coverpage: bytes):
with (
fitz.Document(stream=coverpage, filetype='pdf') as coverpage_pdf,
fitz.Document(stream=content, filetype='pdf') as content_pdf,
):
coverpage_pdf.insert_pdf(content_pdf)
doc = coverpage_pdf.write()
return doc

def test_2791():
'''
Check for memory leaks.
'''
if os.environ.get('PYMUPDF_RUNNING_ON_VALGRIND') == '1':
print(f'test_2791(): not running because PYMUPDF_RUNNING_ON_VALGRIND=1.')
return
#stat_type = 'tracemalloc'
stat_type = 'psutil'
if stat_type == 'tracemalloc':
import tracemalloc
tracemalloc.start(10)
def get_stat():
current, peak = tracemalloc.get_traced_memory()
return current
elif stat_type == 'psutil':
# We use RSS, as used by mprof.
import psutil
process = psutil.Process()
def get_stat():
return process.memory_info().rss
else:
def get_stat():
return 0
n = 1000
stats = [1] * n
for i in range(n):
root = os.path.relpath(f'{__file__}/../../tests/resources')
with (
open(f'{root}/test_2791_content.pdf', 'rb') as content_pdf,
open(f'{root}/test_2791_coverpage.pdf', 'rb') as coverpage_pdf,
):
content = content_pdf.read()
coverpage = coverpage_pdf.read()
merge_pdf(content, coverpage)
sys.stdout.flush()

gc.collect()
stats[i] = get_stat()

print(f'Memory usage {stat_type=}.')
for i, stat in enumerate(stats):
sys.stdout.write(f' {stat}')
#print(f' {i}: {stat}')
sys.stdout.write('\n')
first = stats[2]
last = stats[-1]
ratio = last / first
print(f'{first=} {last=} {ratio=}')

if platform.system() != 'Linux':
# Values from psutil indicate larger memory leaks on non-Linux. Don't
# yet know whether this is because rss is measured differently or a
# genuine leak is being exposed.
print(f'test_2791(): not asserting ratio because not running on Linux.')
elif not hasattr(fitz, 'mupdf'):
# Classic implementation has unfixed leaks.
print(f'test_2791(): not asserting ratio because using classic implementation.')
elif [int(x) for x in platform.python_version_tuple()[:2]] < [3, 11]:
print(f'test_2791(): not asserting ratio because python version less than 3.11: {platform.python_version()=}.')
elif stat_type == 'tracemalloc':
# With tracemalloc Before fix to src/extra.i's calls to
# PyObject_CallMethodObjArgs, ratio was 4.26; after it was 1.40.
assert ratio > 1 and ratio < 1.6
elif stat_type == 'psutil':
# Prior to fix, ratio was 1.043. After the fix, improved to 1.005, but
# varies and sometimes as high as 1.01.
assert ratio >= 1 and ratio < 1.015
else:
pass

0 comments on commit 2c91156

Please sign in to comment.