diff --git a/docs/functions.rst b/docs/functions.rst index b0f9e6fc5..effb641a7 100644 --- a/docs/functions.rst +++ b/docs/functions.rst @@ -117,7 +117,8 @@ Yet others are handy, general-purpose utilities. :arg int fd: Write to file descriptor. :arg stream: - Write to existing stream, which must have methods .write(text) and .flush(). + Write to existing stream, which must have methods `.write(text)` and + `.flush()`. :arg str path: Write to a file. :arg str path_append: diff --git a/scripts/test.py b/scripts/test.py index 8e876cdfe..74b32448a 100755 --- a/scripts/test.py +++ b/scripts/test.py @@ -97,6 +97,8 @@ --valgrind 0|1 Use valgrind in `test` or `buildtest`. This will run `sudo apt update` and `sudo apt install valgrind`. + --valgrind-args + Extra args to valgrind. Commands: build @@ -145,6 +147,7 @@ def main(argv): build_isolation = None valgrind = False + valgrind_args = '' s = True build_do = 'i' build_type = None @@ -216,6 +219,8 @@ def main(argv): gdb = int(next(args)) elif arg == '--valgrind': valgrind = int(next(args)) + elif arg == '--valgrind-args': + valgrind_args = next(args) else: assert 0, f'Unrecognised option: {arg=}.' @@ -259,6 +264,7 @@ def do_test(): test( implementations=implementations, valgrind=valgrind, + valgrind_args=valgrind_args, venv_quick=venv_quick, test_names=test_names, pytest_options=pytest_options, @@ -586,6 +592,7 @@ def write( text, path): def test( implementations, valgrind, + valgrind_args, venv_quick=False, test_names=None, pytest_options=None, @@ -600,6 +607,8 @@ def test( See top-level option `-i`. valgrind: See top-level option `--valgrind`. + valgrind_args: + See top-level option `--valgrind-args`. venv_quick: . test_names: @@ -647,7 +656,7 @@ def test( log('Running PyMuPDF tests under valgrind.') command = ( f'{python} {pymupdf_dir_rel}/tests/run_compound.py{run_compound_args}' - f' valgrind --suppressions={pymupdf_dir_rel}/valgrind.supp --error-exitcode=100 --errors-for-leak-kinds=none --fullpath-after=' + f' valgrind --suppressions={pymupdf_dir_rel}/valgrind.supp --error-exitcode=100 --errors-for-leak-kinds=none --fullpath-after= {valgrind_args}' f' {python} -m pytest {pytest_options}{pytest_arg}' ) env_extra=dict( diff --git a/setup.py b/setup.py index a1c0d601d..a0056433b 100755 --- a/setup.py +++ b/setup.py @@ -658,17 +658,22 @@ def add(flavour, from_, to_): add('b', pipcl.get_soname(f'{mupdf_build_dir}/libmupdf.so'), to_dir) if 'd' in PYMUPDF_SETUP_FLAVOUR: - # Add MuPDF headers to `ret_d`. Would prefer to use + # Add MuPDF C and C++ headers to `ret_d`. Would prefer to use # pipcl.git_items() but hard-coded mupdf tree is not a git # checkout. # - include = f'{mupdf_local}/include' - for dirpath, dirnames, filenames in os.walk(include): - for filename in filenames: - header_abs = os.path.join(dirpath, filename) - assert header_abs.startswith(include) - header_rel = header_abs[len(include)+1:] - add('d', f'{header_abs}', f'{to_dir_d}/include/{header_rel}') + for root in ( + f'{mupdf_local}/include', + f'{mupdf_local}/platform/c++/include', + ): + for dirpath, dirnames, filenames in os.walk(root): + for filename in filenames: + if not filename.endswith('.h'): + continue + header_abs = os.path.join(dirpath, filename) + assert header_abs.startswith(root) + header_rel = header_abs[len(root)+1:] + add('d', f'{header_abs}', f'{to_dir_d}/include/{header_rel}') # Add a .py file containing location of MuPDF. text = f"mupdf_location='{mupdf_location}'\n" diff --git a/src/__init__.py b/src/__init__.py index 24a819494..3002f34ef 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -117,7 +117,7 @@ def _make_output( pylogging_level = int(pylogging_level) pylogging_name = items_d.get('name', 'pymupdf') else: - assert 0, f'Unrecognised {text=}.' + assert 0, f'Expected prefix `fd:`, `path:`. `path+:` or `logging:` in {text=}.' if fd is not None: ret = open(fd, mode='w', closefd=False) @@ -154,10 +154,8 @@ def write(self, text): def flush(self): pass ret = Out() - elif default: - ret = default else: - assert 0, f'No output specified.' + ret = default return ret # Set steam used by PyMuPDF messaging. @@ -259,14 +257,18 @@ def log( text='', caller=1): text = f'{filename}:{line}:{function}(): {text}' if _g_log_items_active: _g_log_items.append(text) - print(text, file=_g_out_log, flush=1) + if _g_out_log: + print(text, file=_g_out_log, flush=1) def message(text=''): ''' For user messages. ''' - print(text, file=_g_out_message, flush=1) + # It looks like `print()` does nothing if sys.stdout is None (without + # raising an exception), but we don't rely on this. + if _g_out_message: + print(text, file=_g_out_message, flush=1) def exception_info(): diff --git a/tests/test_annots.py b/tests/test_annots.py index ea9aa07bc..8fe7b54d4 100644 --- a/tests/test_annots.py +++ b/tests/test_annots.py @@ -419,3 +419,23 @@ def test_3863(): # We get small differences in sysinstall tests, where some # thirdparty libraries can differ. assert rms < 1 + +def test_3758(): + # This test requires input file that is not public, so is usually not + # available. + path = os.path.normpath(f'{__file__}/../../../test_3758.pdf') + if not os.path.exists(path): + print(f'test_3758(): not running because does not exist: {path=}.') + return + import json + with pymupdf.open(path) as document: + for page in document: + info = json.loads(page.get_text('json', flags=pymupdf.TEXTFLAGS_TEXT)) + for block_ind, block in enumerate(info['blocks']): + for line_ind, line in enumerate(block['lines']): + for span_ind, span in enumerate(line['spans']): + # print(span) + page.add_redact_annot(pymupdf.Rect(*span['bbox'])) + page.apply_redactions() + wt = pymupdf.TOOLS.mupdf_warnings() + assert wt diff --git a/tests/test_general.py b/tests/test_general.py index b766fb04d..2d87f22c4 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -1283,6 +1283,20 @@ def check( ], ) + print(f'## Check messages() with sys.stdout=None.') + check( + ''' + import sys + sys.stdout = None + import pymupdf + + pymupdf.message('this is pymupdf.message()') + pymupdf.log('this is pymupdf.log()') + ''', + [], + [], + ) + def relpath(path, start=None): '''