From 1b92f447c3c44440641e87acd1af2bc13bd13b37 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 2 Jun 2020 02:36:41 -0700 Subject: [PATCH] Convert tesseract_crash to plugin --- src/ocrmypdf/exec/tesseract.py | 4 +- tests/plugins/tesseract_crash.py | 64 ++++++++++++++++++++++++++ tests/spoof/tesseract_crash.py | 78 -------------------------------- tests/test_main.py | 26 ++++++----- 4 files changed, 82 insertions(+), 90 deletions(-) create mode 100755 tests/plugins/tesseract_crash.py delete mode 100755 tests/spoof/tesseract_crash.py diff --git a/src/ocrmypdf/exec/tesseract.py b/src/ocrmypdf/exec/tesseract.py index 39046ce5..b1ae3c3c 100644 --- a/src/ocrmypdf/exec/tesseract.py +++ b/src/ocrmypdf/exec/tesseract.py @@ -167,7 +167,9 @@ def get_orientation(input_file: Path, engine_mode, timeout: float, tesseract_env except TimeoutExpired: return OrientationConfidence(angle=0, confidence=0.0) except CalledProcessError as e: - tesseract_log_output(e.output) + # breakpoint() + tesseract_log_output(e.stdout) + tesseract_log_output(e.stderr) if ( b'Too few characters. Skipping this page' in e.output or b'Image too large' in e.output diff --git a/tests/plugins/tesseract_crash.py b/tests/plugins/tesseract_crash.py new file mode 100755 index 00000000..806af41b --- /dev/null +++ b/tests/plugins/tesseract_crash.py @@ -0,0 +1,64 @@ +# © 2020 James R. Barlow: github.com/jbarlow83 +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import signal +import sys +from subprocess import CalledProcessError +from unittest.mock import patch + +from ocrmypdf import hookimpl +from ocrmypdf.builtin_plugins.tesseract_ocr import TesseractOcrEngine + + +def raise_crash(*args, **kwargs): + raise CalledProcessError( + 128 + signal.SIGABRT, + 'tesseract', + output=b"", + stderr=b"libc++abi.dylib: terminating with uncaught exception of type " + + b"std::bad_alloc: std::bad_alloc", + ) + + +class CrashOcrEngine(TesseractOcrEngine): + @staticmethod + def get_orientation(input_file, options): + with patch('ocrmypdf.exec.tesseract.run', new=raise_crash): + return TesseractOcrEngine.get_orientation(input_file, options) + + @staticmethod + def generate_hocr(input_file, output_hocr, output_text, options): + with patch('ocrmypdf.exec.tesseract.run', new=raise_crash): + TesseractOcrEngine.generate_hocr( + input_file, output_hocr, output_text, options + ) + + @staticmethod + def generate_pdf(input_file, output_pdf, output_text, options): + with patch('ocrmypdf.exec.tesseract.run', new=raise_crash): + TesseractOcrEngine.generate_pdf( + input_file, output_pdf, output_text, options + ) + + +@hookimpl +def get_ocr_engine(): + return CrashOcrEngine() diff --git a/tests/spoof/tesseract_crash.py b/tests/spoof/tesseract_crash.py deleted file mode 100755 index 03c7dbde..00000000 --- a/tests/spoof/tesseract_crash.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -# © 2016 James R. Barlow: github.com/jbarlow83 -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import signal -import sys - -VERSION_STRING = '''tesseract 4.0.0 - leptonica-1.77.0 - libjpeg 9c : libpng 1.6.35 : libtiff 4.0.10 : zlib 1.2.11 : libopenjp2 2.3.0 - Found AVX2 - Found AVX - Found SSE -SPOOFED: CRASH ON OCR or --psm 0 -''' - -"""Simulates a Tesseract crash when asked to run OCR - -It isn't strictly necessary to crash the process and that has unwanted -side effects like triggering core dumps or error reporting, logging and such. -It's enough to dump some text to stderr and return an error code. - -Follows the POSIX(?) convention of returning 128 + signal number. - -""" - - -def main(): - if sys.argv[1] == '--version': - print(VERSION_STRING, file=sys.stderr) - sys.exit(0) - elif sys.argv[1] == '--list-langs': - print('List of available languages (1):\neng', file=sys.stderr) - sys.exit(0) - elif sys.argv[-2] == '--print-parameters': - print('A parameter list would go here\ntextonly_pdf 0\n', file=sys.stderr) - sys.exit(0) - elif sys.argv[-2] == 'hocr': - print("KABOOM! Tesseract failed for some reason", file=sys.stderr) - sys.exit(128 + signal.SIGSEGV) - elif sys.argv[-2] == 'pdf': - print("KABOOM! Tesseract failed for some reason", file=sys.stderr) - sys.exit(128 + signal.SIGSEGV) - elif sys.argv[-1] == 'stdout': - print( - "libc++abi.dylib: terminating with uncaught exception of type " - "std::bad_alloc: std::bad_alloc", - file=sys.stderr, - ) - sys.exit(128 + signal.SIGABRT) - else: - print("Spoof doesn't understand arguments", file=sys.stderr) - print(sys.argv, file=sys.stderr) - sys.exit(1) - - sys.exit(0) - - -if __name__ == '__main__': - main() diff --git a/tests/test_main.py b/tests/test_main.py index bf95bd76..b16b31d8 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -45,11 +45,6 @@ spoof = pytest.helpers.spoof RENDERERS = ['hocr', 'sandwich'] -@pytest.fixture -def spoof_tesseract_crash(tmp_path_factory): - return spoof(tmp_path_factory, tesseract='tesseract_crash.py') - - def test_quick(spoof_tesseract_cache, resources, outpdf): check_ocrmypdf(resources / 'ccitt.pdf', outpdf, env=spoof_tesseract_cache) @@ -192,12 +187,16 @@ def test_blank_input_pdf(resources, outpdf): assert result == ExitCode.ok -def test_force_ocr_on_pdf_with_no_images(spoof_tesseract_crash, resources, no_outpdf): +def test_force_ocr_on_pdf_with_no_images(resources, no_outpdf): # 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. p, _, _ = run_ocrmypdf( - resources / 'blank.pdf', no_outpdf, '--force-ocr', env=spoof_tesseract_crash + resources / 'blank.pdf', + no_outpdf, + '--force-ocr', + '--plugin', + 'tests/plugins/tesseract_crash.py', ) assert p.returncode == ExitCode.child_process_error assert not no_outpdf.exists() @@ -304,7 +303,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): +def test_tesseract_crash(renderer, resources, no_outpdf): p, _, err = run_ocrmypdf( resources / 'ccitt.pdf', no_outpdf, @@ -312,16 +311,21 @@ def test_tesseract_crash(renderer, spoof_tesseract_crash, resources, no_outpdf): '1', '--pdf-renderer', renderer, - env=spoof_tesseract_crash, + '--plugin', + 'tests/plugins/tesseract_crash.py', ) assert p.returncode == ExitCode.child_process_error assert not no_outpdf.exists() assert "SubprocessOutputError" in err -def test_tesseract_crash_autorotate(spoof_tesseract_crash, resources, no_outpdf): +def test_tesseract_crash_autorotate(resources, no_outpdf): p, out, err = run_ocrmypdf( - resources / 'ccitt.pdf', no_outpdf, '-r', env=spoof_tesseract_crash + resources / 'ccitt.pdf', + no_outpdf, + '-r', + '--plugin', + 'tests/plugins/tesseract_crash.py', ) assert p.returncode == ExitCode.child_process_error assert not no_outpdf.exists()