From ec93e8a6ad0c6ee9a7d18e138e0d23822a001dd9 Mon Sep 17 00:00:00 2001 From: John Wooten <60260750+JFWooten4@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:29:00 -0400 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=90=9B=20Resolve=20OCR=20Python=20exe?= =?UTF-8?q?cutables=20through=20PATH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accept a working python3 command even when it is not a relative executable file, and inspect OCRmyPDF's shebang only after dependency checks. Cover the startup path with a stubbed OCR regression test. Co-authored-by: Codex Web --- ocr-scanned-pdf.sh | 30 +++++++++++-------- tests/test_ocr_shell_startup.py | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 12 deletions(-) mode change 100755 => 100644 ocr-scanned-pdf.sh create mode 100644 tests/test_ocr_shell_startup.py diff --git a/ocr-scanned-pdf.sh b/ocr-scanned-pdf.sh old mode 100755 new mode 100644 index f95be3a..34a455c --- a/ocr-scanned-pdf.sh +++ b/ocr-scanned-pdf.sh @@ -109,18 +109,6 @@ if [[ ! -f "$tagger" ]]; then exit 1 fi -tagger_python=python3 -if ! "$tagger_python" -c 'import pikepdf' >/dev/null 2>&1; then - ocrmypdf_shebang=$(head -n 1 "$(command -v ocrmypdf)") - tagger_python=${ocrmypdf_shebang#\#!} -fi - -if [[ ! -x "$tagger_python" ]] || \ - ! "$tagger_python" -c 'import pikepdf' >/dev/null 2>&1; then - printf 'Unable to find the Python environment used by OCRmyPDF.\n' >&2 - exit 1 -fi - for command_name in ocrmypdf qpdf; do if ! command -v "$command_name" >/dev/null 2>&1; then printf 'Required command not found: %s\n' "$command_name" >&2 @@ -129,6 +117,24 @@ for command_name in ocrmypdf qpdf; do fi done +# Check the interpreter as a command, not as a relative path named "python3". +# Homebrew commonly installs pikepdf in OCRmyPDF's private Python environment. +tagger_python=(python3) +if ! "${tagger_python[@]}" -c 'import pikepdf' >/dev/null 2>&1; then + ocrmypdf_shebang=$(head -n 1 "$(command -v ocrmypdf)") + if [[ "$ocrmypdf_shebang" == '#!'* ]]; then + read -r -a tagger_python <<< "${ocrmypdf_shebang:2}" + else + tagger_python=() + fi +fi + +if (( ${#tagger_python[@]} == 0 )) || \ + ! "${tagger_python[@]}" -c 'import pikepdf' >/dev/null 2>&1; then + printf 'Unable to find the Python environment used by OCRmyPDF.\n' >&2 + exit 1 +fi + input_pages=$(qpdf --warning-exit-0 --show-npages "$input") printf 'PDF_MAGIC_PROGRESS ocr %s\n' "$input_pages" diff --git a/tests/test_ocr_shell_startup.py b/tests/test_ocr_shell_startup.py new file mode 100644 index 0000000..bdcdf3c --- /dev/null +++ b/tests/test_ocr_shell_startup.py @@ -0,0 +1,52 @@ +"""Regression tests for the shell OCR interpreter setup.""" + +import os +from pathlib import Path +import shlex +import subprocess +import sys +import tempfile +import unittest + + +SCRIPT = Path(__file__).resolve().parents[1] / "ocr-scanned-pdf.sh" + + +class OcrShellStartupTests(unittest.TestCase): + def test_python3_on_path_with_pikepdf_can_start_ocr(self): + """A bare executable name is valid even when ./python3 does not exist.""" + with tempfile.TemporaryDirectory() as folder: + directory = Path(folder) + commands = directory / "bin" + commands.mkdir() + pdf = directory / "source.pdf" + pdf.write_bytes(b"%PDF-1.7\\n") + python3 = commands / "python3" + python3.write_text( + "#!/bin/bash\\n" + 'if [[ "${1:-}" == "-c" && "${2:-}" == "import pikepdf" ]]; then exit 0; fi\\n' + f'exec {shlex.quote(sys.executable)} "$@"\\n' + ) + python3.chmod(0o755) + qpdf = commands / "qpdf" + qpdf.write_text("#!/bin/bash\\necho 1\\n") + qpdf.chmod(0o755) + ocrmypdf = commands / "ocrmypdf" + ocrmypdf.write_text( + "#!/bin/bash\\n" + 'echo "REACHED_OCR_COMMAND" >&2\\n' + "exit 47\\n" + ) + ocrmypdf.chmod(0o755) + environment = {**os.environ, "PATH": str(commands) + os.pathsep + os.environ["PATH"]} + result = subprocess.run( + ["/bin/bash", str(SCRIPT), str(pdf), str(directory / "output.pdf"), "--force-ocr"], + env=environment, capture_output=True, text=True, + ) + self.assertEqual(result.returncode, 47, result.stderr) + self.assertIn("REACHED_OCR_COMMAND", result.stderr) + self.assertNotIn("Unable to find the Python environment", result.stderr) + + +if __name__ == "__main__": + unittest.main() From 06476bcf363bccf80af5075197e09b6bc8e2f670 Mon Sep 17 00:00:00 2001 From: John Wooten <60260750+JFWooten4@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:29:25 -0400 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=90=9B=20Correct=20the=20OCR=20test?= =?UTF-8?q?=20fixture's=20newline=20escapes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate executable shell stubs with actual line breaks so the regression reaches the interpreter-selection path. Co-authored-by: Codex Web --- tests/test_ocr_shell_startup.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_ocr_shell_startup.py b/tests/test_ocr_shell_startup.py index bdcdf3c..d838f17 100644 --- a/tests/test_ocr_shell_startup.py +++ b/tests/test_ocr_shell_startup.py @@ -20,22 +20,22 @@ def test_python3_on_path_with_pikepdf_can_start_ocr(self): commands = directory / "bin" commands.mkdir() pdf = directory / "source.pdf" - pdf.write_bytes(b"%PDF-1.7\\n") + pdf.write_bytes(b"%PDF-1.7\n") python3 = commands / "python3" python3.write_text( - "#!/bin/bash\\n" - 'if [[ "${1:-}" == "-c" && "${2:-}" == "import pikepdf" ]]; then exit 0; fi\\n' - f'exec {shlex.quote(sys.executable)} "$@"\\n' + "#!/bin/bash\n" + 'if [[ "${1:-}" == "-c" && "${2:-}" == "import pikepdf" ]]; then exit 0; fi\n' + f'exec {shlex.quote(sys.executable)} "$@"\n' ) python3.chmod(0o755) qpdf = commands / "qpdf" - qpdf.write_text("#!/bin/bash\\necho 1\\n") + qpdf.write_text("#!/bin/bash\necho 1\n") qpdf.chmod(0o755) ocrmypdf = commands / "ocrmypdf" ocrmypdf.write_text( - "#!/bin/bash\\n" - 'echo "REACHED_OCR_COMMAND" >&2\\n' - "exit 47\\n" + "#!/bin/bash\n" + 'echo "REACHED_OCR_COMMAND" >&2\n' + "exit 47\n" ) ocrmypdf.chmod(0o755) environment = {**os.environ, "PATH": str(commands) + os.pathsep + os.environ["PATH"]} From ced68011627589c2f5b5b8faff06e0257359d2ab Mon Sep 17 00:00:00 2001 From: John Wooten <60260750+JFWooten4@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:34:21 -0400 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=90=9B=20Run=20all=20OCR=20helper=20s?= =?UTF-8?q?cripts=20with=20the=20selected=20Python=20argv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preserve interpreter arguments discovered from OCRmyPDF's shebang when invoking the tagger, reviewer, and sidecar exporter. Co-authored-by: Codex Web --- ocr-scanned-pdf.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ocr-scanned-pdf.sh b/ocr-scanned-pdf.sh index 34a455c..97da4e6 100644 --- a/ocr-scanned-pdf.sh +++ b/ocr-scanned-pdf.sh @@ -163,7 +163,7 @@ if "$ai_review"; then initial_report="$tagging_dir/initial.tagging.json" fi -"$tagger_python" "$tagger" \ +"${tagger_python[@]}" "$tagger" \ "$output" \ "$tagged_output" \ --report "$initial_report" \ @@ -172,11 +172,11 @@ fi if "$ai_review"; then printf 'PDF_MAGIC_PROGRESS review %s\n' "$input_pages" - "$tagger_python" "$script_dir/ollama-review-ocr.py" \ + "${tagger_python[@]}" "$script_dir/ollama-review-ocr.py" \ "$tagged_output" "$initial_report" "$review_report" reviewed_output="$tagging_dir/reviewed.pdf" printf 'PDF_MAGIC_PROGRESS finalizing\n' - "$tagger_python" "$tagger" \ + "${tagger_python[@]}" "$tagger" \ "$output" "$reviewed_output" \ --review "$review_report" \ --report "$tagging_report" \ @@ -187,7 +187,7 @@ fi # OCRmyPDF's sidecar omits skipped pages. Build it from the tagging report so # preserved embedded text remains searchable in the exported plain text too. -"$tagger_python" - "$tagging_report" "$sidecar" <<'PYTEXT' +"${tagger_python[@]}" - "$tagging_report" "$sidecar" <<'PYTEXT' import json from pathlib import Path import sys From 037cc275d2676e6e5038741d6ec1108aec0510ac Mon Sep 17 00:00:00 2001 From: John Wooten <60260750+JFWooten4@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:34:38 -0400 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=90=9B=20Run=20OCR=20startup=20regres?= =?UTF-8?q?sion=20in=20pull=20request=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Include OCR shell changes in the existing regression workflow and execute its startup test alongside metadata checks. Co-authored-by: Codex Web --- .github/workflows/pdf-regressions.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pdf-regressions.yml b/.github/workflows/pdf-regressions.yml index 20d4ab2..378f2fc 100644 --- a/.github/workflows/pdf-regressions.yml +++ b/.github/workflows/pdf-regressions.yml @@ -7,6 +7,8 @@ on: paths: - "pdf_metadata.py" - "tag-ocr-pdf.py" + - "ocr-scanned-pdf.sh" + - "tests/test_ocr_shell_startup.py" - "tests/test_pdf_metadata.py" - "tests/test_tag_existing_text.py" - ".github/workflows/pdf-regressions.yml" @@ -21,5 +23,7 @@ jobs: python-version: "3.12" - name: Install regression dependencies run: python -m pip install pikepdf pdfminer.six + - name: Run OCR shell startup regression + run: bash -n ocr-scanned-pdf.sh && python -m unittest discover -s tests -p 'test_ocr_shell_startup.py' -v - name: Run metadata and tagged PDF tests run: python -m unittest discover -s tests -p 'test_pdf_metadata.py' -v && python -m unittest discover -s tests -p 'test_tag_existing_text.py' -v From e30a81c2480b4a8e9b27a2233d6cf029344a7ac7 Mon Sep 17 00:00:00 2001 From: John Wooten <60260750+JFWooten4@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:40:00 -0400 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=A7=B0=20Prefer=20an=20isolated=20Enh?= =?UTF-8?q?ancer=20Python=20virtual=20environment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dedicated setup script for the project .venv, prioritize it for OCR and compression helpers, retain backward-compatible interpreter selection for existing installations, and test venv precedence and diagnostics. Co-authored-by: Codex Web --- .github/workflows/pdf-regressions.yml | 4 +- .gitignore | 1 + OCR.md | 19 ++++++++ compress-and-tag-ocr-pdf.sh | 37 +++++++++++----- ocr-scanned-pdf.sh | 40 ++++++++++------- setup-python-env.sh | 19 ++++++++ tests/test_ocr_shell_startup.py | 63 +++++++++++++++++++++++++++ 7 files changed, 155 insertions(+), 28 deletions(-) mode change 100755 => 100644 compress-and-tag-ocr-pdf.sh create mode 100755 setup-python-env.sh diff --git a/.github/workflows/pdf-regressions.yml b/.github/workflows/pdf-regressions.yml index 378f2fc..548bcf0 100644 --- a/.github/workflows/pdf-regressions.yml +++ b/.github/workflows/pdf-regressions.yml @@ -8,6 +8,8 @@ on: - "pdf_metadata.py" - "tag-ocr-pdf.py" - "ocr-scanned-pdf.sh" + - "compress-and-tag-ocr-pdf.sh" + - "setup-python-env.sh" - "tests/test_ocr_shell_startup.py" - "tests/test_pdf_metadata.py" - "tests/test_tag_existing_text.py" @@ -24,6 +26,6 @@ jobs: - name: Install regression dependencies run: python -m pip install pikepdf pdfminer.six - name: Run OCR shell startup regression - run: bash -n ocr-scanned-pdf.sh && python -m unittest discover -s tests -p 'test_ocr_shell_startup.py' -v + run: bash -n ocr-scanned-pdf.sh && bash -n compress-and-tag-ocr-pdf.sh && bash -n setup-python-env.sh && python -m unittest discover -s tests -p 'test_ocr_shell_startup.py' -v - name: Run metadata and tagged PDF tests run: python -m unittest discover -s tests -p 'test_pdf_metadata.py' -v && python -m unittest discover -s tests -p 'test_tag_existing_text.py' -v diff --git a/.gitignore b/.gitignore index 7a22182..f7bcc1f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__/ .google-books-browser/ *-pages/ +.venv/ diff --git a/OCR.md b/OCR.md index b15e77e..4cd75d9 100644 --- a/OCR.md +++ b/OCR.md @@ -3,6 +3,25 @@ These tools create reproducible searchable, structured, and losslessly compressed derivatives of scanned PDFs without replacing the source. +## Python environment + +Install the Enhancer's isolated Python dependencies once: + +```sh +./setup-python-env.sh +``` + +This creates `.venv/` beside the Enhancer scripts with `pikepdf` and Pillow. +The OCR and compression scripts prefer `.venv/bin/python` for tagging, local AI +review, and metadata helpers. Existing standalone installations without this +environment can still use Python with the required packages, including the +interpreter bundled with OCRmyPDF. An incomplete project venv produces an error +instead of silently using an unrelated global interpreter. + +The browser viewer's native-host installer also initializes this environment +when an Enhancer checkout supplies `setup-python-env.sh`. OCRmyPDF and qpdf +remain separate system executables, typically installed using Homebrew. + ## Create searchable OCR Run the OCR, paragraph grouping, table-block classification, and heading diff --git a/compress-and-tag-ocr-pdf.sh b/compress-and-tag-ocr-pdf.sh old mode 100755 new mode 100644 index a3343f3..3bf679d --- a/compress-and-tag-ocr-pdf.sh +++ b/compress-and-tag-ocr-pdf.sh @@ -53,23 +53,36 @@ for command_name in ocrmypdf qpdf; do fi done -tagger_python=python3 -if ! "$tagger_python" -c 'import pikepdf, PIL' >/dev/null 2>&1; then - ocrmypdf_shebang=$(head -n 1 "$(command -v ocrmypdf)") - tagger_python=${ocrmypdf_shebang#\#!} -fi - -if [[ ! -x "$tagger_python" ]] || \ - ! "$tagger_python" -c 'import pikepdf, PIL' >/dev/null 2>&1; then - printf 'Unable to find the Python environment used by OCRmyPDF.\n' >&2 - exit 1 +venv_python="$script_dir/.venv/bin/python" +if [[ -e "$venv_python" || -L "$venv_python" ]]; then + if [[ ! -x "$venv_python" ]] || ! "$venv_python" -c 'import pikepdf, PIL' >/dev/null 2>&1; then + printf 'PDF Enhancer virtual environment needs repair: %s\n' "$venv_python" >&2 + printf 'Run %s/setup-python-env.sh to install its dependencies.\n' "$script_dir" >&2 + exit 1 + fi + tagger_python=("$venv_python") +else + tagger_python=(python3) + if ! "${tagger_python[@]}" -c 'import pikepdf, PIL' >/dev/null 2>&1; then + ocrmypdf_shebang=$(head -n 1 "$(command -v ocrmypdf)") + if [[ "$ocrmypdf_shebang" == '#!'* ]]; then + read -r -a tagger_python <<< "${ocrmypdf_shebang:2}" + else + tagger_python=() + fi + fi + if (( ${#tagger_python[@]} == 0 )) || \ + ! "${tagger_python[@]}" -c 'import pikepdf, PIL' >/dev/null 2>&1; then + printf 'No Python with pikepdf/Pillow found; run %s/setup-python-env.sh.\n' "$script_dir" >&2 + exit 1 + fi fi work_dir=$(mktemp -d "${TMPDIR:-/tmp}/compress-tag-pdf.XXXXXX") trap 'rm -rf -- "$work_dir"' EXIT compressed="$work_dir/compressed.pdf" -"$tagger_python" "$script_dir/compress-pdf-images.py" \ +"${tagger_python[@]}" "$script_dir/compress-pdf-images.py" \ "$input" "$compressed" \ --report "$compression_report" \ --reported-output "$output" @@ -88,7 +101,7 @@ if [[ -n "$review" ]]; then fi tagger_args+=(--review "$review") fi -"$tagger_python" "$script_dir/tag-ocr-pdf.py" "${tagger_args[@]}" +"${tagger_python[@]}" "$script_dir/tag-ocr-pdf.py" "${tagger_args[@]}" qpdf --check "$output" input_pages=$(qpdf --show-npages "$input") diff --git a/ocr-scanned-pdf.sh b/ocr-scanned-pdf.sh index 97da4e6..6935345 100644 --- a/ocr-scanned-pdf.sh +++ b/ocr-scanned-pdf.sh @@ -117,22 +117,32 @@ for command_name in ocrmypdf qpdf; do fi done -# Check the interpreter as a command, not as a relative path named "python3". -# Homebrew commonly installs pikepdf in OCRmyPDF's private Python environment. -tagger_python=(python3) -if ! "${tagger_python[@]}" -c 'import pikepdf' >/dev/null 2>&1; then - ocrmypdf_shebang=$(head -n 1 "$(command -v ocrmypdf)") - if [[ "$ocrmypdf_shebang" == '#!'* ]]; then - read -r -a tagger_python <<< "${ocrmypdf_shebang:2}" - else - tagger_python=() +# Prefer the Enhancer's own isolated Python dependencies when installed. +# Keep the prior interpreter discovery for existing standalone installations. +venv_python="$script_dir/.venv/bin/python" +if [[ -e "$venv_python" || -L "$venv_python" ]]; then + if [[ ! -x "$venv_python" ]] || ! "$venv_python" -c 'import pikepdf' >/dev/null 2>&1; then + printf 'PDF Enhancer virtual environment needs repair: %s\n' "$venv_python" >&2 + printf 'Run %s/setup-python-env.sh to install its dependencies.\n' "$script_dir" >&2 + exit 1 + fi + tagger_python=("$venv_python") +else + # With no project venv, support preexisting Python and OCRmyPDF environments. + tagger_python=(python3) + if ! "${tagger_python[@]}" -c 'import pikepdf' >/dev/null 2>&1; then + ocrmypdf_shebang=$(head -n 1 "$(command -v ocrmypdf)") + if [[ "$ocrmypdf_shebang" == '#!'* ]]; then + read -r -a tagger_python <<< "${ocrmypdf_shebang:2}" + else + tagger_python=() + fi + fi + if (( ${#tagger_python[@]} == 0 )) || \ + ! "${tagger_python[@]}" -c 'import pikepdf' >/dev/null 2>&1; then + printf 'No Python with pikepdf found; run %s/setup-python-env.sh.\n' "$script_dir" >&2 + exit 1 fi -fi - -if (( ${#tagger_python[@]} == 0 )) || \ - ! "${tagger_python[@]}" -c 'import pikepdf' >/dev/null 2>&1; then - printf 'Unable to find the Python environment used by OCRmyPDF.\n' >&2 - exit 1 fi input_pages=$(qpdf --warning-exit-0 --show-npages "$input") diff --git a/setup-python-env.sh b/setup-python-env.sh new file mode 100755 index 0000000..b0867f0 --- /dev/null +++ b/setup-python-env.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Install only Enhancer's Python dependencies; Homebrew manages the OCR binaries. +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +venv_python="$script_dir/.venv/bin/python" +if [[ ! -x "$venv_python" ]]; then + bootstrap_python="${PDF_MAGIC_BOOTSTRAP_PYTHON:-python3}" + if ! command -v "$bootstrap_python" >/dev/null 2>&1; then + printf 'Python 3 is required to create the Enhancer virtual environment.\n' >&2 + exit 1 + fi + "$bootstrap_python" -m venv "$script_dir/.venv" +fi +if ! "$venv_python" -c 'import pikepdf, PIL' >/dev/null 2>&1; then + "$venv_python" -m pip install --disable-pip-version-check pikepdf Pillow +fi +"$venv_python" -c 'import pikepdf, PIL' +printf 'PDF Enhancer Python environment ready: %s\n' "$venv_python" diff --git a/tests/test_ocr_shell_startup.py b/tests/test_ocr_shell_startup.py index d838f17..f8471f5 100644 --- a/tests/test_ocr_shell_startup.py +++ b/tests/test_ocr_shell_startup.py @@ -48,5 +48,68 @@ def test_python3_on_path_with_pikepdf_can_start_ocr(self): self.assertNotIn("Unable to find the Python environment", result.stderr) + def test_project_venv_wins_over_python_on_path(self): + with tempfile.TemporaryDirectory() as folder: + directory = Path(folder) + script = directory / "ocr-scanned-pdf.sh" + script.write_bytes(SCRIPT.read_bytes()) + (directory / "tag-ocr-pdf.py").write_text("# placeholder\n") + venv = directory / ".venv" / "bin" + venv.mkdir(parents=True) + venv_python = venv / "python" + venv_python.write_text( + "#!/bin/bash\n" + 'if [[ "${1:-}" == "-c" ]]; then echo "PROJECT_VENV_SELECTED" >&2; exit 0; fi\n' + "exit 83\n" + ) + venv_python.chmod(0o755) + commands = directory / "bin" + commands.mkdir() + for name, code in { + "qpdf": "#!/bin/bash\necho 1\n", + "ocrmypdf": '#!/bin/bash\necho "REACHED_OCR" >&2\nexit 47\n', + "python3": '#!/bin/bash\necho "INCORRECT_SYSTEM_PYTHON" >&2\nexit 0\n', + }.items(): + executable = commands / name + executable.write_text(code) + executable.chmod(0o755) + pdf = directory / "scan.pdf" + pdf.write_bytes(b"%PDF-1.7\n") + env = {**os.environ, "PATH": str(commands) + os.pathsep + os.environ["PATH"]} + result = subprocess.run( + ["/bin/bash", str(script), str(pdf), str(directory / "enhanced.pdf")], + env=env, capture_output=True, text=True, + ) + self.assertEqual(result.returncode, 47, result.stderr) + self.assertIn("PROJECT_VENV_SELECTED", result.stderr) + self.assertNotIn("INCORRECT_SYSTEM_PYTHON", result.stderr) + + def test_incomplete_project_venv_fails_without_falling_back_to_system_python(self): + with tempfile.TemporaryDirectory() as folder: + directory = Path(folder) + script = directory / "ocr-scanned-pdf.sh" + script.write_bytes(SCRIPT.read_bytes()) + (directory / "tag-ocr-pdf.py").write_text("# placeholder\n") + venv = directory / ".venv" / "bin" + venv.mkdir(parents=True) + executable = venv / "python" + executable.write_text("#!/bin/bash\nexit 1\n") + executable.chmod(0o755) + commands = directory / "bin" + commands.mkdir() + for name in ("qpdf", "ocrmypdf", "python3"): + shim = commands / name + shim.write_text("#!/bin/bash\nexit 0\n") + shim.chmod(0o755) + pdf = directory / "scan.pdf" + pdf.write_bytes(b"%PDF-1.7\n") + env = {**os.environ, "PATH": str(commands) + os.pathsep + os.environ["PATH"]} + result = subprocess.run( + ["/bin/bash", str(script), str(pdf)], + env=env, capture_output=True, text=True, + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("setup-python-env.sh", result.stderr) + if __name__ == "__main__": unittest.main() From 2edd374dd1ce98056bd58f9b2796b48aea6d978a Mon Sep 17 00:00:00 2001 From: John Wooten <60260750+JFWooten4@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:42:10 -0400 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A7=AA=20Verify=20virtualenv=20select?= =?UTF-8?q?ion=20without=20relying=20on=20suppressed=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assert file markers from test interpreter shims because dependency checks intentionally redirect output to /dev/null. Co-authored-by: Codex Web --- tests/test_ocr_shell_startup.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_ocr_shell_startup.py b/tests/test_ocr_shell_startup.py index f8471f5..10db270 100644 --- a/tests/test_ocr_shell_startup.py +++ b/tests/test_ocr_shell_startup.py @@ -59,7 +59,7 @@ def test_project_venv_wins_over_python_on_path(self): venv_python = venv / "python" venv_python.write_text( "#!/bin/bash\n" - 'if [[ "${1:-}" == "-c" ]]; then echo "PROJECT_VENV_SELECTED" >&2; exit 0; fi\n' + 'if [[ "${1:-}" == "-c" ]]; then touch "$PDF_MAGIC_TEST_VENV_MARKER"; exit 0; fi\n' "exit 83\n" ) venv_python.chmod(0o755) @@ -68,21 +68,25 @@ def test_project_venv_wins_over_python_on_path(self): for name, code in { "qpdf": "#!/bin/bash\necho 1\n", "ocrmypdf": '#!/bin/bash\necho "REACHED_OCR" >&2\nexit 47\n', - "python3": '#!/bin/bash\necho "INCORRECT_SYSTEM_PYTHON" >&2\nexit 0\n', + "python3": '#!/bin/bash\ntouch "$PDF_MAGIC_TEST_SYSTEM_MARKER"\nexit 0\n', }.items(): executable = commands / name executable.write_text(code) executable.chmod(0o755) pdf = directory / "scan.pdf" pdf.write_bytes(b"%PDF-1.7\n") - env = {**os.environ, "PATH": str(commands) + os.pathsep + os.environ["PATH"]} + env = { + **os.environ, "PATH": str(commands) + os.pathsep + os.environ["PATH"], + "PDF_MAGIC_TEST_VENV_MARKER": str(directory / "venv-selected"), + "PDF_MAGIC_TEST_SYSTEM_MARKER": str(directory / "system-selected"), + } result = subprocess.run( ["/bin/bash", str(script), str(pdf), str(directory / "enhanced.pdf")], env=env, capture_output=True, text=True, ) self.assertEqual(result.returncode, 47, result.stderr) - self.assertIn("PROJECT_VENV_SELECTED", result.stderr) - self.assertNotIn("INCORRECT_SYSTEM_PYTHON", result.stderr) + self.assertTrue((directory / "venv-selected").exists()) + self.assertFalse((directory / "system-selected").exists()) def test_incomplete_project_venv_fails_without_falling_back_to_system_python(self): with tempfile.TemporaryDirectory() as folder: