2022-07-28 01:06:46 -07:00
|
|
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
|
|
|
# SPDX-License-Identifier: MPL-2.0
|
2019-12-30 15:52:10 -08:00
|
|
|
|
2022-07-23 00:39:24 -07:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2019-12-30 15:52:10 -08:00
|
|
|
import logging
|
|
|
|
import multiprocessing
|
2020-05-28 14:58:41 -07:00
|
|
|
import os
|
2022-08-02 14:39:23 -07:00
|
|
|
from pathlib import Path
|
2019-12-30 15:52:10 -08:00
|
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
|
|
import pytest
|
2022-08-02 14:39:23 -07:00
|
|
|
from packaging.version import Version
|
2019-12-30 15:52:10 -08:00
|
|
|
|
2021-04-07 02:09:45 -07:00
|
|
|
from ocrmypdf import helpers
|
2024-10-27 11:55:22 -07:00
|
|
|
from ocrmypdf.helpers import running_in_docker
|
2021-04-07 01:56:51 -07:00
|
|
|
|
2021-07-14 00:11:47 -07:00
|
|
|
needs_symlink = pytest.mark.skipif(os.name == 'nt', reason='needs posix symlink')
|
2023-06-02 02:47:41 -07:00
|
|
|
windows_only = pytest.mark.skipif(os.name != 'nt', reason="Windows test")
|
2021-07-14 00:11:47 -07:00
|
|
|
|
2019-12-30 15:52:10 -08:00
|
|
|
|
|
|
|
class TestSafeSymlink:
|
|
|
|
def test_safe_symlink_link_self(self, tmp_path, caplog):
|
|
|
|
helpers.safe_symlink(tmp_path / 'self', tmp_path / 'self')
|
|
|
|
assert caplog.record_tuples[0][1] == logging.WARNING
|
|
|
|
|
|
|
|
def test_safe_symlink_overwrite(self, tmp_path):
|
|
|
|
(tmp_path / 'regular_file').touch()
|
|
|
|
with pytest.raises(FileExistsError):
|
|
|
|
helpers.safe_symlink(tmp_path / 'input', tmp_path / 'regular_file')
|
|
|
|
|
2021-07-14 00:11:47 -07:00
|
|
|
@needs_symlink
|
2019-12-30 15:52:10 -08:00
|
|
|
def test_safe_symlink_relink(self, tmp_path):
|
|
|
|
(tmp_path / 'regular_file_a').touch()
|
|
|
|
(tmp_path / 'regular_file_b').write_bytes(b'ABC')
|
|
|
|
(tmp_path / 'link').symlink_to(tmp_path / 'regular_file_a')
|
|
|
|
helpers.safe_symlink(tmp_path / 'regular_file_b', tmp_path / 'link')
|
|
|
|
assert (tmp_path / 'link').samefile(tmp_path / 'regular_file_b') or (
|
|
|
|
tmp_path / 'link'
|
|
|
|
).read_bytes() == b'ABC'
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_cpu_count(monkeypatch):
|
2020-12-28 23:51:55 -08:00
|
|
|
invoked = False
|
|
|
|
|
2019-12-30 15:52:10 -08:00
|
|
|
def cpu_count_raises():
|
2020-12-28 23:51:55 -08:00
|
|
|
nonlocal invoked
|
|
|
|
invoked = True
|
2019-12-30 15:52:10 -08:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
monkeypatch.setattr(multiprocessing, 'cpu_count', cpu_count_raises)
|
|
|
|
with pytest.warns(expected_warning=UserWarning):
|
|
|
|
assert helpers.available_cpu_count() == 1
|
2020-12-28 23:51:55 -08:00
|
|
|
assert invoked, "Patched function called during test"
|
2019-12-30 15:52:10 -08:00
|
|
|
|
|
|
|
|
2021-04-07 01:56:51 -07:00
|
|
|
skipif_docker = pytest.mark.skipif(running_in_docker(), reason="fails on Docker")
|
2020-06-21 01:48:13 -07:00
|
|
|
|
|
|
|
|
2019-12-30 15:52:10 -08:00
|
|
|
class TestFileIsWritable:
|
|
|
|
@pytest.fixture
|
|
|
|
def non_existent(self, tmp_path):
|
|
|
|
return tmp_path / 'nofile'
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def basic_file(self, tmp_path):
|
|
|
|
basic = tmp_path / 'basic'
|
|
|
|
basic.touch()
|
|
|
|
return basic
|
|
|
|
|
|
|
|
def test_plain(self, non_existent):
|
|
|
|
assert helpers.is_file_writable(non_existent)
|
|
|
|
|
2021-07-14 00:11:47 -07:00
|
|
|
@needs_symlink
|
2019-12-30 15:52:10 -08:00
|
|
|
def test_symlink_loop(self, tmp_path):
|
|
|
|
loop = tmp_path / 'loop'
|
|
|
|
loop.symlink_to(loop)
|
|
|
|
assert not helpers.is_file_writable(loop)
|
|
|
|
|
2020-06-21 01:48:13 -07:00
|
|
|
@skipif_docker
|
2019-12-30 15:52:10 -08:00
|
|
|
def test_chmod(self, basic_file):
|
|
|
|
assert helpers.is_file_writable(basic_file)
|
|
|
|
basic_file.chmod(0o400)
|
|
|
|
assert not helpers.is_file_writable(basic_file)
|
|
|
|
basic_file.chmod(0o000)
|
|
|
|
assert not helpers.is_file_writable(basic_file)
|
|
|
|
|
|
|
|
def test_permission_error(self, basic_file):
|
|
|
|
pathmock = MagicMock(spec_set=basic_file)
|
|
|
|
pathmock.is_symlink.return_value = False
|
|
|
|
pathmock.exists.return_value = True
|
|
|
|
pathmock.is_file.side_effect = PermissionError
|
|
|
|
assert not helpers.is_file_writable(pathmock)
|
2020-05-28 14:58:41 -07:00
|
|
|
|
|
|
|
|
2023-06-02 02:47:41 -07:00
|
|
|
@windows_only
|
2022-08-02 14:39:23 -07:00
|
|
|
def test_gs_install_locations():
|
|
|
|
# pylint: disable=import-outside-toplevel
|
|
|
|
from ocrmypdf.subprocess._windows import _gs_version_in_path_key
|
|
|
|
|
|
|
|
assert _gs_version_in_path_key(Path("C:\\Program Files\\gs\\gs9.52\\bin")) == (
|
|
|
|
'gs',
|
|
|
|
Version('9.52'),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-06-02 02:47:41 -07:00
|
|
|
@windows_only
|
2020-05-28 14:58:41 -07:00
|
|
|
def test_shim_paths(tmp_path):
|
2021-04-07 02:09:45 -07:00
|
|
|
# pylint: disable=import-outside-toplevel
|
2020-12-02 16:00:12 -08:00
|
|
|
from ocrmypdf.subprocess._windows import shim_env_path
|
|
|
|
|
2020-05-28 14:58:41 -07:00
|
|
|
progfiles = tmp_path / 'Program Files'
|
|
|
|
progfiles.mkdir()
|
|
|
|
(progfiles / 'tesseract-ocr').mkdir()
|
|
|
|
(progfiles / 'gs' / '9.51' / 'bin').mkdir(parents=True)
|
2022-08-02 14:39:23 -07:00
|
|
|
(progfiles / 'gs' / 'gs9.52.3' / 'bin').mkdir(parents=True)
|
2020-05-28 14:58:41 -07:00
|
|
|
syspath = tmp_path / 'bin'
|
|
|
|
env = {'PROGRAMFILES': str(progfiles), 'PATH': str(syspath)}
|
|
|
|
|
2020-12-02 16:00:12 -08:00
|
|
|
result_str = shim_env_path(env=env)
|
2020-05-28 14:58:41 -07:00
|
|
|
results = result_str.split(os.pathsep)
|
2020-12-02 16:00:12 -08:00
|
|
|
assert results[0] == str(syspath), results
|
|
|
|
assert results[-3].endswith('tesseract-ocr'), results
|
2022-08-02 14:39:23 -07:00
|
|
|
assert results[-2].endswith(os.path.join('gs9.52.3', 'bin')), results
|
2020-12-02 16:00:12 -08:00
|
|
|
assert results[-1].endswith(os.path.join('gs', '9.51', 'bin')), results
|
2021-04-07 23:26:37 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_resolution():
|
|
|
|
Resolution = helpers.Resolution
|
|
|
|
dpi_100 = Resolution(100, 100)
|
|
|
|
dpi_200 = Resolution(200, 200)
|
|
|
|
assert dpi_100.is_square
|
|
|
|
assert not Resolution(100, 200).is_square
|
|
|
|
assert dpi_100 == Resolution(100, 100)
|
|
|
|
assert str(dpi_100) != str(dpi_200)
|
|
|
|
assert dpi_100.take_max([200, 300], [400]) == Resolution(300, 400)
|