9090 Whether to rebuild mupdf when we build PyMuPDF. Default is 1.
9191 --gdb 0|1
9292 Run tests under gdb.
93+ --pybind 0|1
94+ Run tests inside C++ pybind.Requires `sudo apt install pybind11-dev` or
95+ similar.
9396 --system-site-packages 0|1
9497 If 1, use `--system-site-packages` when creating venv.
9598 --timeout <seconds>
@@ -165,6 +168,7 @@ def main(argv):
165168 implementations = 'r'
166169 test_names = list ()
167170 venv = 2
171+ pybind = False
168172 pytest_options = None
169173 timeout = None
170174 pytest_k = None
@@ -211,6 +215,8 @@ def main(argv):
211215 value = next (args )
212216 assert value in ('0' , '1' ), f'`-s` must be followed by `0` or `1`, not { value = } .'
213217 os .environ ['PYMUPDF_SETUP_PY_LIMITED_API' ] = value
218+ elif arg == '--pybind' :
219+ pybind = int (next (args ))
214220 elif arg == '--system-site-packages' :
215221 system_site_packages = int (next (args ))
216222 elif arg == '-t' :
@@ -282,6 +288,7 @@ def do_test():
282288 gdb = gdb ,
283289 test_fitz = test_fitz ,
284290 pytest_k = pytest_k ,
291+ pybind = pybind ,
285292 )
286293
287294 for command in commands :
@@ -621,7 +628,8 @@ def test(
621628 timeout = None ,
622629 gdb = False ,
623630 test_fitz = True ,
624- pytest_k = None
631+ pytest_k = None ,
632+ pybind = False ,
625633 ):
626634 '''
627635 Args:
@@ -642,6 +650,65 @@ def test(
642650 test_fitz:
643651 See top-level option `-f`.
644652 '''
653+ if pybind :
654+ cpp_path = 'pymupdf_test_pybind.cpp'
655+ cpp_exe = 'pymupdf_test_pybind.exe'
656+ cpp = textwrap .dedent ('''
657+ #include <pybind11/embed.h>
658+
659+ int main()
660+ {
661+ pybind11::scoped_interpreter guard{};
662+ pybind11::exec(R"(
663+ print('Hello world', flush=1)
664+ import pymupdf
665+ pymupdf.JM_mupdf_show_warnings = 1
666+ print(f'{pymupdf.version=}', flush=1)
667+ doc = pymupdf.Document()
668+ pymupdf.mupdf.fz_warn('Dummy warning.')
669+ pymupdf.mupdf.fz_warn('Dummy warning.')
670+ pymupdf.mupdf.fz_warn('Dummy warning.')
671+ print(f'{doc=}', flush=1)
672+ )");
673+ }
674+ ''' )
675+ def fs_read (path ):
676+ try :
677+ with open (path ) as f :
678+ return f .read ()
679+ except Exception :
680+ return
681+ def fs_remove (path ):
682+ try :
683+ os .remove (path )
684+ except Exception :
685+ pass
686+ cpp_existing = fs_read (cpp_path )
687+ if cpp == cpp_existing :
688+ log (f'Not creating { cpp_exe } because unchanged: { cpp_path } ' )
689+ else :
690+ with open (cpp_path , 'w' ) as f :
691+ f .write (cpp )
692+ def getmtime (path ):
693+ try :
694+ return os .path .getmtime (path )
695+ except Exception :
696+ return 0
697+ python_config = f'{ os .path .realpath (sys .executable )} -config'
698+ # `--embed` adds `-lpython3.11` to the link command, which appears to
699+ # be necessary when building an executable.
700+ flags = run (f'{ python_config } --cflags --ldflags --embed' , capture = 1 )
701+ build_command = f'c++ { cpp_path } -o { cpp_exe } -g -W -Wall { flags } '
702+ build_path = f'{ cpp_exe } .cmd'
703+ build_command_prev = fs_read (build_path )
704+ if build_command != build_command_prev or getmtime (cpp_path ) >= getmtime (cpp_exe ):
705+ fs_remove (build_path )
706+ run (build_command )
707+ with open (build_path , 'w' ) as f :
708+ f .write (build_command )
709+ run (f'./{ cpp_exe } ' )
710+ return
711+
645712 pymupdf_dir_rel = gh_release .relpath (pymupdf_dir )
646713 if pytest_options is None :
647714 if valgrind :
0 commit comments