diff --git a/setup.cfg b/setup.cfg index 3cb3db9d..487ed30d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,7 @@ force_grid_wrap=0 use_parentheses=True line_length=88 known_first_party = ocrmypdf -known_third_party = PIL,_cffi_backend,cffi,flask,gs,img2pdf,pdfminer,pikepdf,pkg_resources,pluggy,pytest,reportlab,setuptools,sphinx_rtd_theme,tqdm,watchdog,werkzeug +known_third_party = PIL,_cffi_backend,cffi,flask,img2pdf,pdfminer,pikepdf,pkg_resources,pluggy,pytest,reportlab,setuptools,sphinx_rtd_theme,tqdm,watchdog,werkzeug [metadata] license_file = LICENSE diff --git a/tests/spoof/gs_feature_elision.py b/tests/plugins/gs_feature_elision.py old mode 100755 new mode 100644 similarity index 59% rename from tests/spoof/gs_feature_elision.py rename to tests/plugins/gs_feature_elision.py index a06deaf3..84f5e6e1 --- a/tests/spoof/gs_feature_elision.py +++ b/tests/plugins/gs_feature_elision.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# © 2016 James R. Barlow: github.com/jbarlow83 +# © 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 @@ -20,34 +19,31 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from unittest.mock import patch -import os -import sys -from subprocess import check_call - -from gs import real_ghostscript - -"""Replicate one type of Ghostscript feature elision warning during -PDF/A creation.""" - +from ocrmypdf import hookimpl +from ocrmypdf.builtin_plugins import ghostscript +from ocrmypdf.exec import run elision_warning = """GPL Ghostscript 9.20: Setting Overprint Mode to 1 not permitted in PDF/A-2, overprint mode not set""" -def main(): - if '--version' in sys.argv: - print('9.20') - print('SPOOFED: ' + os.path.basename(__file__)) - sys.exit(0) - gs_args = ['gs'] + sys.argv[1:] - check_call(gs_args) - - if '-sDEVICE=pdfwrite' in sys.argv[1:]: - print(elision_warning) - - sys.exit(0) +def run_append_stderr(*args, **kwargs): + proc = run(*args, **kwargs) + proc.stderr = b'\n'.join([proc.stderr, elision_warning.encode('utf-8')]) + return proc -if __name__ == '__main__': - main() +@hookimpl +def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part): + with patch('ocrmypdf.exec.ghostscript.run', new=run_append_stderr): + ghostscript.generate_pdfa( + pdf_pages=pdf_pages, + pdfmark=pdfmark, + output_file=output_file, + compression=compression, + pdf_version=pdf_version, + pdfa_part=pdfa_part, + ) + return output_file diff --git a/tests/spoof/gs_pdfa_failure.py b/tests/plugins/gs_pdfa_failure.py old mode 100755 new mode 100644 similarity index 59% rename from tests/spoof/gs_pdfa_failure.py rename to tests/plugins/gs_pdfa_failure.py index 1d9fdf7d..f9093224 --- a/tests/spoof/gs_pdfa_failure.py +++ b/tests/plugins/gs_pdfa_failure.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# © 2016 James R. Barlow: github.com/jbarlow83 +# © 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 @@ -20,41 +19,33 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import os -import sys +from unittest.mock import patch -from gs import real_ghostscript +from ocrmypdf import hookimpl +from ocrmypdf.builtin_plugins import ghostscript +from ocrmypdf.exec import run -"""Replicate Ghostscript PDF/A conversion failure by suppressing some -arguments""" - - -def main(): - if '--version' in sys.argv: - print('9.20') - print('SPOOFED: ' + os.path.basename(__file__)) - sys.exit(0) - - # Unless some argument is calling for PDFA generation, forward to - # real ghostscript - if not any(arg.startswith('-dPDFA') for arg in sys.argv): - real_ghostscript(sys.argv) - return - +def run_rig_args(args, **kwargs): # Remove the two arguments that tell ghostscript to create a PDF/A # Does not remove the Postscript definition file - not necessary # to cause PDF/A creation failure - argv = [] - for arg in sys.argv: - if arg.startswith('-dPDFA'): - continue - elif arg.startswith('-dPDFACompatibilityPolicy'): - continue - argv.append(arg) - - real_ghostscript(argv) + new_args = [ + arg for arg in args if not arg.startswith('-dPDFA') and not arg.endswith('.ps') + ] + proc = run(new_args, **kwargs) + return proc -if __name__ == '__main__': - main() +@hookimpl +def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part): + with patch('ocrmypdf.exec.ghostscript.run', new=run_rig_args): + ghostscript.generate_pdfa( + pdf_pages=pdf_pages, + pdfmark=pdfmark, + output_file=output_file, + compression=compression, + pdf_version=pdf_version, + pdfa_part=pdfa_part, + ) + return output_file diff --git a/tests/spoof/gs_raster_failure.py b/tests/plugins/gs_raster_failure.py old mode 100755 new mode 100644 similarity index 51% rename from tests/spoof/gs_raster_failure.py rename to tests/plugins/gs_raster_failure.py index 7619aae2..cab3268e --- a/tests/spoof/gs_raster_failure.py +++ b/tests/plugins/gs_raster_failure.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# © 2016 James R. Barlow: github.com/jbarlow83 +# © 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 @@ -20,30 +19,41 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from pathlib import Path +from subprocess import CalledProcessError +from unittest.mock import patch -import os -import sys - -from gs import real_ghostscript - -"""Replicate Ghostscript raster failure while allowing rendering""" +from ocrmypdf import hookimpl +from ocrmypdf.builtin_plugins import ghostscript +from ocrmypdf.exec import run -def main(): - if '--version' in sys.argv: - print('9.20') - print('SPOOFED: ' + os.path.basename(__file__)) - sys.exit(0) - - # For non-image rastering calls, use real ghostscript - if '-sDEVICE=pdfwrite' in sys.argv or '-sDEVICE=txtwrite' in sys.argv: - real_ghostscript(sys.argv) - return - - # Fail - print("ERROR: Ghost story archive not found", file=sys.stderr) - sys.exit(1) +def raise_gs_fail(*args, **kwargs): + raise CalledProcessError( + 1, 'gs', output=b"", stderr=b"ERROR: Ghost story archive not found" + ) -if __name__ == '__main__': - main() +@hookimpl +def rasterize_pdf_page( + input_file, + output_file, + raster_device, + raster_dpi, + pageno, + page_dpi=None, + rotation=None, + filter_vector=False, +) -> Path: + with patch('ocrmypdf.exec.ghostscript.run', new=raise_gs_fail): + ghostscript.rasterize_pdf_page( + input_file=input_file, + output_file=output_file, + raster_device=raster_device, + raster_dpi=raster_dpi, + pageno=pageno, + page_dpi=page_dpi, + rotation=rotation, + filter_vector=filter_vector, + ) + return output_file diff --git a/tests/spoof/gs_render_failure.py b/tests/plugins/gs_render_failure.py old mode 100755 new mode 100644 similarity index 55% rename from tests/spoof/gs_render_failure.py rename to tests/plugins/gs_render_failure.py index d0c1d60d..e1d934ce --- a/tests/spoof/gs_render_failure.py +++ b/tests/plugins/gs_render_failure.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# © 2016-18 James R. Barlow: github.com/jbarlow83 +# © 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 @@ -20,29 +19,30 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Replicate Ghostscript render failure while allowing rasterizing""" +from pathlib import Path +from subprocess import CalledProcessError +from unittest.mock import patch -import os -import sys - -from gs import real_ghostscript +from ocrmypdf import hookimpl +from ocrmypdf.builtin_plugins import ghostscript +from ocrmypdf.exec import run -def main(): - if '--version' in sys.argv: - print('9.20') - print('SPOOFED: ' + os.path.basename(__file__)) - sys.exit(0) - - # For any rasterize calls (device != pdfwrite) call real ghostscript - if '-sDEVICE=pdfwrite' not in sys.argv: - real_ghostscript(sys.argv) - return - - # Fail - print("ERROR: Casper is not a friendly ghost", file=sys.stderr) - sys.exit(1) +def raise_gs_fail(*args, **kwargs): + raise CalledProcessError( + 1, 'gs', output=b"", stderr=b"ERROR: Casper is not a friendly ghost" + ) -if __name__ == '__main__': - main() +@hookimpl +def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part): + with patch('ocrmypdf.exec.ghostscript.run', new=raise_gs_fail): + ghostscript.generate_pdfa( + pdf_pages=pdf_pages, + pdfmark=pdfmark, + output_file=output_file, + compression=compression, + pdf_version=pdf_version, + pdfa_part=pdfa_part, + ) + return output_file diff --git a/tests/test_ghostscript.py b/tests/test_ghostscript.py index 0e6931df..a58a3c04 100644 --- a/tests/test_ghostscript.py +++ b/tests/test_ghostscript.py @@ -32,26 +32,6 @@ run_ocrmypdf_api = pytest.helpers.run_ocrmypdf_api spoof = pytest.helpers.spoof -@pytest.fixture -def spoof_gs_render_fail(tmp_path_factory): - return spoof(tmp_path_factory, gs='gs_render_failure.py') - - -@pytest.fixture -def spoof_gs_raster_fail(tmp_path_factory): - return spoof(tmp_path_factory, gs='gs_raster_failure.py') - - -@pytest.fixture -def spoof_no_pdfa(tmp_path_factory): - return spoof(tmp_path_factory, gs='gs_pdfa_failure.py') - - -@pytest.fixture -def spoof_pdfa_warning(tmp_path_factory): - return spoof(tmp_path_factory, gs='gs_feature_elision.py') - - @pytest.fixture def francais(resources): path = resources / 'francais.pdf' @@ -106,48 +86,52 @@ def test_rasterize_rotated(francais, outdir, caplog): assert im.info['dpi'] == (forced_dpi[1], forced_dpi[0]) -def test_gs_render_failure(spoof_gs_render_fail, resources, outpdf): +def test_gs_render_failure(resources, outpdf): p, out, err = run_ocrmypdf( resources / 'blank.pdf', outpdf, '--plugin', 'tests/plugins/tesseract_noop.py', - env=spoof_gs_render_fail, + '--plugin', + 'tests/plugins/gs_render_failure.py', ) assert 'Casper is not a friendly ghost' in err assert p.returncode == ExitCode.child_process_error -def test_gs_raster_failure(spoof_gs_raster_fail, resources, outpdf): +def test_gs_raster_failure(resources, outpdf): p, out, err = run_ocrmypdf( resources / 'francais.pdf', outpdf, '--plugin', 'tests/plugins/tesseract_noop.py', - env=spoof_gs_raster_fail, + '--plugin', + 'tests/plugins/gs_raster_failure.py', ) assert 'Ghost story archive not found' in err assert p.returncode == ExitCode.child_process_error -def test_ghostscript_pdfa_failure(spoof_no_pdfa, resources, outpdf): +def test_ghostscript_pdfa_failure(resources, outpdf): p, out, err = run_ocrmypdf( resources / 'francais.pdf', outpdf, '--plugin', 'tests/plugins/tesseract_noop.py', - env=spoof_no_pdfa, + '--plugin', + 'tests/plugins/gs_pdfa_failure.py', ) assert ( p.returncode == ExitCode.pdfa_conversion_failed ), "Unexpected return when PDF/A fails" -def test_ghostscript_feature_elision(spoof_pdfa_warning, resources, outpdf): +def test_ghostscript_feature_elision(resources, outpdf): check_ocrmypdf( resources / 'francais.pdf', outpdf, '--plugin', 'tests/plugins/tesseract_noop.py', - env=spoof_pdfa_warning, + '--plugin', + 'tests/plugins/gs_feature_elision.py', )