Use _OCRMYPDF_TEST_PATH for testing and .py stubs to simulate symlinks
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -21,14 +21,35 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import shutil
|
||||
from collections.abc import Mapping
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError, run
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError, run as subprocess_run
|
||||
|
||||
from ..exceptions import ExitCode, MissingDependencyError
|
||||
|
||||
log = logging.Logger(__name__)
|
||||
|
||||
|
||||
def _get_program(args, env=None):
|
||||
program = args[0]
|
||||
test_path = env.get('_OCRMYPDF_TEST_PATH', '')
|
||||
if test_path:
|
||||
program = shutil.which(program, path=test_path)
|
||||
return program
|
||||
|
||||
|
||||
def run(args, *, env=None, **kwargs):
|
||||
if not env:
|
||||
env = os.environ
|
||||
program = _get_program(args, env)
|
||||
if os.name == 'nt' and program.lower().endswith('.py'):
|
||||
args = [sys.executable, program] + args[1:]
|
||||
else:
|
||||
args = [program] + args[1:]
|
||||
log.debug(args)
|
||||
return subprocess_run(args, env=env, **kwargs)
|
||||
|
||||
|
||||
def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)', env=None):
|
||||
"Get the version of the specified program"
|
||||
args_prog = [program, version_arg]
|
||||
|
||||
@@ -26,13 +26,13 @@ from functools import lru_cache
|
||||
from io import BytesIO
|
||||
from os import fspath
|
||||
from pathlib import Path
|
||||
from subprocess import PIPE, run, CalledProcessError
|
||||
from subprocess import PIPE, CalledProcessError
|
||||
from shutil import which
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from ..exceptions import SubprocessOutputError, MissingDependencyError
|
||||
from . import get_version
|
||||
from . import get_version, run
|
||||
|
||||
gslog = logging.getLogger()
|
||||
|
||||
@@ -43,6 +43,7 @@ if os.name == 'nt':
|
||||
GS = which('gswin32c')
|
||||
if not GS:
|
||||
raise MissingDependencyError("Ghostscript (gswin64c or gswin32c)")
|
||||
GS = Path(GS).stem
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
"""Interface to jbig2 executable"""
|
||||
|
||||
from functools import lru_cache
|
||||
from subprocess import PIPE, run
|
||||
from subprocess import PIPE
|
||||
|
||||
from ..exceptions import MissingDependencyError
|
||||
from . import get_version
|
||||
from . import get_version, run
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
from functools import lru_cache
|
||||
from os import fspath
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError, run
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError
|
||||
|
||||
from . import get_version
|
||||
from . import get_version, run
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
|
||||
@@ -23,7 +23,7 @@ from collections import namedtuple
|
||||
from contextlib import suppress
|
||||
import logging
|
||||
from os import fspath
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired, run
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
|
||||
|
||||
from ..exceptions import (
|
||||
MissingDependencyError,
|
||||
@@ -31,7 +31,7 @@ from ..exceptions import (
|
||||
TesseractConfigError,
|
||||
)
|
||||
from ..helpers import page_number, safe_symlink
|
||||
from . import get_version
|
||||
from . import get_version, run
|
||||
|
||||
OrientationConfidence = namedtuple('OrientationConfidence', ('angle', 'confidence'))
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from functools import lru_cache
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError
|
||||
from tempfile import TemporaryDirectory
|
||||
@@ -30,7 +29,7 @@ from tempfile import TemporaryDirectory
|
||||
from PIL import Image
|
||||
|
||||
from ..exceptions import MissingDependencyError, SubprocessOutputError
|
||||
from . import get_version
|
||||
from . import get_version, run as external_run
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
@@ -77,7 +76,7 @@ def run(input_file, output_file, dpi, log, mode_args):
|
||||
# their unpaper arguments (whether intentionally or otherwise)
|
||||
args_unpaper.extend([input_pnm, output_pnm])
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
proc = external_run(
|
||||
args_unpaper,
|
||||
check=True,
|
||||
close_fds=True,
|
||||
|
||||
+31
-6
@@ -80,6 +80,19 @@ PROJECT_ROOT = os.path.dirname(TESTS_ROOT)
|
||||
OCRMYPDF = [sys.executable, '-m', 'ocrmypdf']
|
||||
|
||||
|
||||
PY_FILE_TEMPLATE = """
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
args = [sys.executable, {spoofer}, *sys.argv[1:]]
|
||||
p = subprocess.run(args, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
sys.stdout.buffer.write(p.stdout)
|
||||
sys.stderr.buffer.write(p.stderr)
|
||||
sys.exit(p.returncode)
|
||||
"""
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def spoof(tmp_path_factory, **kwargs):
|
||||
"""Modify PATH to override subprocess executables
|
||||
@@ -97,12 +110,24 @@ def spoof(tmp_path_factory, **kwargs):
|
||||
|
||||
for replace_program, with_spoof in kwargs.items():
|
||||
spoofer = Path(SPOOF_PATH) / with_spoof
|
||||
spoofer.chmod(0o755)
|
||||
(tmpdir / replace_program).symlink_to(spoofer)
|
||||
|
||||
env['_OCRMYPDF_SAVE_PATH'] = env['PATH']
|
||||
env['PATH'] = str(tmpdir) + ":" + env['PATH']
|
||||
if os.name != 'nt':
|
||||
spoofer.chmod(0o755)
|
||||
(tmpdir / replace_program).symlink_to(spoofer)
|
||||
else:
|
||||
py_file = PY_FILE_TEMPLATE.format(
|
||||
python=sys.executable, spoofer=repr(os.fspath(spoofer.absolute()))
|
||||
)
|
||||
if replace_program == 'gs':
|
||||
programs = ['gswin64c', 'gswin32c']
|
||||
else:
|
||||
programs = [replace_program]
|
||||
for prog in programs:
|
||||
(tmpdir / f'{prog}.py').write_text(py_file, encoding='utf-8')
|
||||
|
||||
env['_OCRMYPDF_TEST_PATH'] = str(tmpdir) + os.pathsep + env['PATH']
|
||||
if os.name == 'nt':
|
||||
if '.py' not in env['PATHEXT'].lower():
|
||||
raise EnvironmentError("PATHEXT is not configured to support .py")
|
||||
return env
|
||||
|
||||
|
||||
@@ -178,7 +203,7 @@ def run_ocrmypdf_api(input_file, output_file, *args, env=None):
|
||||
)
|
||||
api.check_options(options)
|
||||
if env:
|
||||
options.tesseract_env = env
|
||||
options.tesseract_env = env.copy()
|
||||
options.tesseract_env['_OCRMYPDF_TEST_INFILE'] = os.fspath(input_file)
|
||||
if options.tesseract_env:
|
||||
assert all(isinstance(v, (str, bytes)) for v in options.tesseract_env.values())
|
||||
|
||||
@@ -38,7 +38,6 @@ not permitted in PDF/A-2, overprint mode not set"""
|
||||
|
||||
|
||||
def main():
|
||||
os.environ['PATH'] = os.environ['_OCRMYPDF_SAVE_PATH']
|
||||
if '--version' in sys.argv:
|
||||
print('9.20')
|
||||
print('SPOOFED: ' + os.path.basename(__file__))
|
||||
|
||||
@@ -31,7 +31,6 @@ from gs import real_ghostscript
|
||||
|
||||
|
||||
def main():
|
||||
os.environ['PATH'] = os.environ['_OCRMYPDF_SAVE_PATH']
|
||||
if '--version' in sys.argv:
|
||||
print('9.20')
|
||||
print('SPOOFED: ' + os.path.basename(__file__))
|
||||
|
||||
@@ -31,7 +31,6 @@ from gs import real_ghostscript
|
||||
|
||||
|
||||
def main():
|
||||
os.environ['PATH'] = os.environ['_OCRMYPDF_SAVE_PATH']
|
||||
if '--version' in sys.argv:
|
||||
print('9.20')
|
||||
print('SPOOFED: ' + os.path.basename(__file__))
|
||||
|
||||
@@ -30,7 +30,6 @@ from gs import real_ghostscript
|
||||
|
||||
|
||||
def main():
|
||||
os.environ['PATH'] = os.environ['_OCRMYPDF_SAVE_PATH']
|
||||
if '--version' in sys.argv:
|
||||
print('9.20')
|
||||
print('SPOOFED: ' + os.path.basename(__file__))
|
||||
|
||||
@@ -59,8 +59,6 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if '_OCRMYPDF_SAVE_PATH' in os.environ:
|
||||
os.environ['PATH'] = os.environ['_OCRMYPDF_SAVE_PATH']
|
||||
|
||||
__version__ = subprocess.check_output(
|
||||
['tesseract', '--version'], stderr=subprocess.STDOUT
|
||||
|
||||
+5
-5
@@ -297,10 +297,10 @@ def test_force_ocr_on_pdf_with_no_images(spoof_tesseract_crash, resources, no_ou
|
||||
# As a correctness test, make sure that --force-ocr on a PDF with no
|
||||
# content still triggers tesseract. If tesseract crashes, then it was
|
||||
# called.
|
||||
result = run_ocrmypdf_api(
|
||||
p, _, _ = run_ocrmypdf(
|
||||
resources / 'blank.pdf', no_outpdf, '--force-ocr', env=spoof_tesseract_crash
|
||||
)
|
||||
assert result == ExitCode.child_process_error
|
||||
assert p.returncode == ExitCode.child_process_error
|
||||
assert not os.path.exists(no_outpdf)
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ def test_pagesegmode(renderer, spoof_tesseract_cache, resources, outpdf):
|
||||
|
||||
@pytest.mark.parametrize('renderer', RENDERERS)
|
||||
def test_tesseract_crash(renderer, spoof_tesseract_crash, resources, no_outpdf, caplog):
|
||||
result = run_ocrmypdf_api(
|
||||
p, _, err = run_ocrmypdf(
|
||||
resources / 'ccitt.pdf',
|
||||
no_outpdf,
|
||||
'-v',
|
||||
@@ -398,9 +398,9 @@ def test_tesseract_crash(renderer, spoof_tesseract_crash, resources, no_outpdf,
|
||||
renderer,
|
||||
env=spoof_tesseract_crash,
|
||||
)
|
||||
assert result == ExitCode.child_process_error
|
||||
assert p.returncode == ExitCode.child_process_error
|
||||
assert not os.path.exists(no_outpdf)
|
||||
assert "SubprocessOutputError" in caplog.text
|
||||
assert "SubprocessOutputError" in err
|
||||
|
||||
|
||||
def test_tesseract_crash_autorotate(spoof_tesseract_crash, resources, no_outpdf):
|
||||
|
||||
Reference in New Issue
Block a user