mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 18:02:38 +02:00
Fix isolation provider tests
Conversions methods had changed and that was part of the reason why the tests were failing. Furthermore, due to the `provider.proc`, which stores the associated qrexec / container process, "server" exceptions raise a IterruptedConversion error (now ConverterProcException), which then requires interpretation of the process exit code to obtain the "real" exception.
This commit is contained in:
parent
0a54f6461a
commit
61e7a3c107
5 changed files with 33 additions and 25 deletions
|
@ -1,4 +1,4 @@
|
||||||
from typing import List, Optional, Type
|
from typing import List, Optional, Type, Union
|
||||||
|
|
||||||
# XXX: errors start at 128 for conversion-related issues
|
# XXX: errors start at 128 for conversion-related issues
|
||||||
ERROR_SHIFT = 128
|
ERROR_SHIFT = 128
|
||||||
|
@ -86,8 +86,8 @@ class PageCountMismatch(PagesException):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InterruptedConversion(ConversionException):
|
class ConverterProcException(ConversionException):
|
||||||
"""Protocol received num of bytes different than expected"""
|
"""Some exception occurred in the converter"""
|
||||||
|
|
||||||
error_code = ERROR_SHIFT + 60
|
error_code = ERROR_SHIFT + 60
|
||||||
error_message = (
|
error_message = (
|
||||||
|
@ -100,7 +100,9 @@ class UnexpectedConversionError(ConversionException):
|
||||||
error_message = "Some unexpected error occurred while converting the document"
|
error_message = "Some unexpected error occurred while converting the document"
|
||||||
|
|
||||||
|
|
||||||
def exception_from_error_code(error_code: int) -> Optional[ConversionException]:
|
def exception_from_error_code(
|
||||||
|
error_code: int,
|
||||||
|
) -> Union[ConversionException, ValueError]:
|
||||||
"""returns the conversion exception corresponding to the error code"""
|
"""returns the conversion exception corresponding to the error code"""
|
||||||
for cls in ConversionException.get_subclasses():
|
for cls in ConversionException.get_subclasses():
|
||||||
if cls.error_code == error_code:
|
if cls.error_code == error_code:
|
||||||
|
|
|
@ -27,7 +27,7 @@ def read_bytes(f: IO[bytes], size: int, timeout: float, exact: bool = True) -> b
|
||||||
"""Read bytes from a file-like object."""
|
"""Read bytes from a file-like object."""
|
||||||
buf = nonblocking_read(f, size, timeout)
|
buf = nonblocking_read(f, size, timeout)
|
||||||
if exact and len(buf) != size:
|
if exact and len(buf) != size:
|
||||||
raise errors.InterruptedConversion
|
raise errors.ConverterProcException()
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,11 +80,9 @@ class IsolationProvider(ABC):
|
||||||
document.mark_as_safe()
|
document.mark_as_safe()
|
||||||
if document.archive_after_conversion:
|
if document.archive_after_conversion:
|
||||||
document.archive()
|
document.archive()
|
||||||
except errors.InterruptedConversion:
|
except errors.ConverterProcException:
|
||||||
assert self.proc is not None
|
exception = self.get_proc_exception()
|
||||||
error_code = self.proc.wait(3)
|
self.print_progress(document, True, str(exception), 0)
|
||||||
# XXX Reconstruct exception from error code
|
|
||||||
exception = errors.exception_from_error_code(error_code)
|
|
||||||
document.mark_as_failed()
|
document.mark_as_failed()
|
||||||
except errors.ConversionException as e:
|
except errors.ConversionException as e:
|
||||||
self.print_progress(document, True, str(e), 0)
|
self.print_progress(document, True, str(e), 0)
|
||||||
|
@ -105,7 +103,7 @@ class IsolationProvider(ABC):
|
||||||
self.proc.stdin.write(f.read())
|
self.proc.stdin.write(f.read())
|
||||||
self.proc.stdin.close()
|
self.proc.stdin.close()
|
||||||
except BrokenPipeError as e:
|
except BrokenPipeError as e:
|
||||||
raise errors.InterruptedConversion()
|
raise errors.ConverterProcException()
|
||||||
|
|
||||||
# Get file size (in MiB)
|
# Get file size (in MiB)
|
||||||
size = os.path.getsize(document.input_filename) / 1024**2
|
size = os.path.getsize(document.input_filename) / 1024**2
|
||||||
|
@ -188,6 +186,12 @@ class IsolationProvider(ABC):
|
||||||
if self.progress_callback:
|
if self.progress_callback:
|
||||||
self.progress_callback(error, text, percentage)
|
self.progress_callback(error, text, percentage)
|
||||||
|
|
||||||
|
def get_proc_exception(self) -> Exception:
|
||||||
|
"""Returns an exception associated with a process exit code"""
|
||||||
|
assert self.proc
|
||||||
|
error_code = self.proc.wait(3)
|
||||||
|
return errors.exception_from_error_code(error_code)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_max_parallel_conversions(self) -> int:
|
def get_max_parallel_conversions(self) -> int:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -237,7 +237,7 @@ class Container(IsolationProvider):
|
||||||
error_code = pixels_to_pdf_proc.wait()
|
error_code = pixels_to_pdf_proc.wait()
|
||||||
if error_code != 0:
|
if error_code != 0:
|
||||||
log.error("pixels-to-pdf failed")
|
log.error("pixels-to-pdf failed")
|
||||||
raise errors.exception_from_error_code(error_code) # type: ignore [misc]
|
raise errors.exception_from_error_code(error_code)
|
||||||
else:
|
else:
|
||||||
# Move the final file to the right place
|
# Move the final file to the right place
|
||||||
if os.path.exists(document.output_filename):
|
if os.path.exists(document.output_filename):
|
||||||
|
|
|
@ -18,14 +18,15 @@ from .. import pdf_11k_pages, sanitized_text, uncommon_text
|
||||||
)
|
)
|
||||||
@pytest.mark.skipif(not running_on_qubes(), reason="Not on a Qubes system")
|
@pytest.mark.skipif(not running_on_qubes(), reason="Not on a Qubes system")
|
||||||
class IsolationProviderTest:
|
class IsolationProviderTest:
|
||||||
def test_max_pages_received(
|
def test_max_pages_server_enforcement(
|
||||||
self,
|
self,
|
||||||
pdf_11k_pages: str,
|
pdf_11k_pages: str,
|
||||||
provider: base.IsolationProvider,
|
provider: base.IsolationProvider,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
|
tmpdir: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
provider.progress_callback = mocker.MagicMock()
|
provider.progress_callback = mocker.MagicMock()
|
||||||
doc = Document(pdf_11k_pages)
|
doc = Document(pdf_11k_pages)
|
||||||
with pytest.raises(errors.MaxPagesException):
|
with pytest.raises(errors.ConverterProcException):
|
||||||
success = provider._convert(doc, ocr_lang=None)
|
provider.doc_to_pixels(doc, tmpdir)
|
||||||
assert not success
|
assert provider.get_proc_exception() == errors.MaxPagesException
|
||||||
|
|
|
@ -30,11 +30,12 @@ def provider() -> Qubes:
|
||||||
|
|
||||||
@pytest.mark.skipif(not running_on_qubes(), reason="Not on a Qubes system")
|
@pytest.mark.skipif(not running_on_qubes(), reason="Not on a Qubes system")
|
||||||
class TestQubes(IsolationProviderTest):
|
class TestQubes(IsolationProviderTest):
|
||||||
def test_max_pages_client_side_enforcement(
|
def test_max_pages_client_enforcement(
|
||||||
self,
|
self,
|
||||||
sample_doc: str,
|
sample_doc: str,
|
||||||
provider: Qubes,
|
provider: Qubes,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
|
tmpdir: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
provider.progress_callback = mocker.MagicMock()
|
provider.progress_callback = mocker.MagicMock()
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
|
@ -42,8 +43,7 @@ class TestQubes(IsolationProviderTest):
|
||||||
) # sample_doc has 4 pages > 1
|
) # sample_doc has 4 pages > 1
|
||||||
doc = Document(sample_doc)
|
doc = Document(sample_doc)
|
||||||
with pytest.raises(errors.MaxPagesException):
|
with pytest.raises(errors.MaxPagesException):
|
||||||
success = provider._convert(doc, ocr_lang=None)
|
provider.doc_to_pixels(doc, tmpdir)
|
||||||
assert not success
|
|
||||||
|
|
||||||
def test_max_dimensions(
|
def test_max_dimensions(
|
||||||
self,
|
self,
|
||||||
|
@ -51,14 +51,13 @@ class TestQubes(IsolationProviderTest):
|
||||||
sample_bad_height: str,
|
sample_bad_height: str,
|
||||||
provider: Qubes,
|
provider: Qubes,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
|
tmpdir: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
provider.progress_callback = mocker.MagicMock()
|
provider.progress_callback = mocker.MagicMock()
|
||||||
with pytest.raises(errors.MaxPageWidthException):
|
with pytest.raises(errors.MaxPageWidthException):
|
||||||
success = provider._convert(Document(sample_bad_width), ocr_lang=None)
|
provider.doc_to_pixels(Document(sample_bad_width), tmpdir)
|
||||||
assert not success
|
|
||||||
with pytest.raises(errors.MaxPageHeightException):
|
with pytest.raises(errors.MaxPageHeightException):
|
||||||
success = provider._convert(Document(sample_bad_height), ocr_lang=None)
|
provider.doc_to_pixels(Document(sample_bad_height), tmpdir)
|
||||||
assert not success
|
|
||||||
|
|
||||||
def test_out_of_ram(
|
def test_out_of_ram(
|
||||||
self,
|
self,
|
||||||
|
@ -66,6 +65,7 @@ class TestQubes(IsolationProviderTest):
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
monkeypatch: MonkeyPatch,
|
monkeypatch: MonkeyPatch,
|
||||||
sample_doc: str,
|
sample_doc: str,
|
||||||
|
tmpdir: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
provider.progress_callback = mocker.MagicMock()
|
provider.progress_callback = mocker.MagicMock()
|
||||||
|
|
||||||
|
@ -85,6 +85,7 @@ class TestQubes(IsolationProviderTest):
|
||||||
provider, "start_doc_to_pixels_proc", start_doc_to_pixels_proc
|
provider, "start_doc_to_pixels_proc", start_doc_to_pixels_proc
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(errors.QubesQrexecFailed) as e:
|
with pytest.raises(errors.ConverterProcException) as e:
|
||||||
doc = Document(sample_doc)
|
doc = Document(sample_doc)
|
||||||
provider._convert(doc, ocr_lang=None)
|
provider.doc_to_pixels(doc, tmpdir)
|
||||||
|
assert provider.get_proc_exception() == errors.QubesQrexecFailed
|
||||||
|
|
Loading…
Reference in a new issue