mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-29 10:12:38 +02:00
tests: Wrap Click results with extra functionality
Wrap Click results (`Result`) with a new class (`CLIResult`), which includes: 1. Assertion statements. 2. Logic for formatting and printing a Click result. 3. Invocation arguments, which are missing from the original `Result` class.
This commit is contained in:
parent
a6c2b943f4
commit
6b7797639c
1 changed files with 107 additions and 11 deletions
|
@ -1,8 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import traceback
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner, Result
|
from click.testing import CliRunner, Result
|
||||||
|
@ -24,21 +28,113 @@ from . import TestBase, for_each_doc
|
||||||
# FIXME "/" path separator is platform-dependent, use pathlib instead
|
# FIXME "/" path separator is platform-dependent, use pathlib instead
|
||||||
|
|
||||||
|
|
||||||
|
class CLIResult(Result):
|
||||||
|
"""Wrapper class for Click results.
|
||||||
|
|
||||||
|
This class wraps Click results and provides the following extras:
|
||||||
|
* Assertion statements for success/failure.
|
||||||
|
* Printing the result details, when an assertion fails.
|
||||||
|
* The arguments of the invocation, which are not provided by the stock
|
||||||
|
`Result` class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reclass_click_result(cls, result: Result, args: Sequence[str]) -> CLIResult:
|
||||||
|
result.__class__ = cls
|
||||||
|
result.args = copy.deepcopy(args) # type: ignore[attr-defined]
|
||||||
|
return result # type: ignore[return-value]
|
||||||
|
|
||||||
|
def assert_success(self) -> None:
|
||||||
|
"""Assert that the command succeeded."""
|
||||||
|
try:
|
||||||
|
assert self.exit_code == 0
|
||||||
|
assert self.exception is None
|
||||||
|
except AssertionError:
|
||||||
|
self.print_info()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def assert_failure(self, exit_code: int = None, message: str = None) -> None:
|
||||||
|
"""Assert that the command failed.
|
||||||
|
|
||||||
|
By default, check that the command has returned with an exit code
|
||||||
|
other than 0. Alternatively, the caller can check for a specific exit
|
||||||
|
code. Also, the caller can check if the output contains an error
|
||||||
|
message.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if exit_code is None:
|
||||||
|
assert self.exit_code != 0
|
||||||
|
else:
|
||||||
|
assert self.exit_code == exit_code
|
||||||
|
if message is not None:
|
||||||
|
assert message in self.output
|
||||||
|
except AssertionError:
|
||||||
|
self.print_info()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def print_info(self) -> None:
|
||||||
|
"""Print all the info we have for a CLI result.
|
||||||
|
|
||||||
|
Print the string representation of the result, as well as:
|
||||||
|
1. Command output (if any).
|
||||||
|
2. Exception traceback (if any).
|
||||||
|
"""
|
||||||
|
print(self)
|
||||||
|
num_lines = len(self.output.splitlines())
|
||||||
|
if num_lines > 0:
|
||||||
|
print(f"Output ({num_lines} lines follow):")
|
||||||
|
print(self.output)
|
||||||
|
else:
|
||||||
|
print("Output (0 lines).")
|
||||||
|
|
||||||
|
if self.exc_info:
|
||||||
|
print("The original traceback follows:")
|
||||||
|
traceback.print_exception(*self.exc_info, file=sys.stdout)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""Return a string representation of a CLI result.
|
||||||
|
|
||||||
|
Include the arguments of the command invocation, as well as the exit
|
||||||
|
code and exception message.
|
||||||
|
"""
|
||||||
|
desc = (
|
||||||
|
f"<CLIResult args: {self.args}," # type: ignore[attr-defined]
|
||||||
|
f" exit code: {self.exit_code}"
|
||||||
|
)
|
||||||
|
if self.exception:
|
||||||
|
desc += f", exception: {self.exception}"
|
||||||
|
return desc
|
||||||
|
|
||||||
|
|
||||||
class TestCli(TestBase):
|
class TestCli(TestBase):
|
||||||
def run_cli(self, *args, **kwargs) -> Result:
|
def run_cli(self, args: Sequence[str] | str = ()) -> CLIResult:
|
||||||
return CliRunner().invoke(cli_main, *args, **kwargs)
|
"""Run the CLI with the provided arguments.
|
||||||
|
|
||||||
|
Callers can either provide a list of arguments (iterable), or a single
|
||||||
|
argument (str). Note that in both cases, we don't tokenize the input
|
||||||
|
(i.e., perform `shlex.split()`), as this is prone to errors in Windows
|
||||||
|
environments [1]. The user must perform the tokenizaton themselves.
|
||||||
|
|
||||||
|
[1]: https://stackoverflow.com/a/35900070c
|
||||||
|
"""
|
||||||
|
if isinstance(args, str):
|
||||||
|
# Convert the single argument to a tuple, else Click will attempt
|
||||||
|
# to tokenize it.
|
||||||
|
args = (args,)
|
||||||
|
result = CliRunner().invoke(cli_main, args)
|
||||||
|
return CLIResult.reclass_click_result(result, args)
|
||||||
|
|
||||||
|
|
||||||
class TestCliBasic(TestCli):
|
class TestCliBasic(TestCli):
|
||||||
def test_no_args(self):
|
def test_no_args(self):
|
||||||
"""``$ dangerzone-cli``"""
|
"""``$ dangerzone-cli``"""
|
||||||
result = self.run_cli()
|
result = self.run_cli()
|
||||||
assert result.exit_code != 0
|
result.assert_failure()
|
||||||
|
|
||||||
def test_help(self):
|
def test_help(self):
|
||||||
"""``$ dangerzone-cli --help``"""
|
"""``$ dangerzone-cli --help``"""
|
||||||
result = self.run_cli("--help")
|
result = self.run_cli("--help")
|
||||||
assert result.exit_code == 0
|
result.assert_success()
|
||||||
|
|
||||||
def test_display_banner(self, capfd):
|
def test_display_banner(self, capfd):
|
||||||
display_banner() # call the test subject
|
display_banner() # call the test subject
|
||||||
|
@ -55,33 +151,33 @@ class TestCliBasic(TestCli):
|
||||||
class TestCliConversion(TestCliBasic):
|
class TestCliConversion(TestCliBasic):
|
||||||
def test_invalid_lang(self):
|
def test_invalid_lang(self):
|
||||||
result = self.run_cli(f"{self.sample_doc} --ocr-lang piglatin")
|
result = self.run_cli(f"{self.sample_doc} --ocr-lang piglatin")
|
||||||
assert result.exit_code != 0
|
result.assert_failure()
|
||||||
|
|
||||||
@for_each_doc
|
@for_each_doc
|
||||||
def test_formats(self, doc):
|
def test_formats(self, doc):
|
||||||
result = self.run_cli(f'"{doc}"')
|
result = self.run_cli(f'"{doc}"')
|
||||||
assert result.exit_code == 0
|
result.assert_success()
|
||||||
|
|
||||||
def test_output_filename(self):
|
def test_output_filename(self):
|
||||||
temp_dir = tempfile.mkdtemp(prefix="dangerzone-")
|
temp_dir = tempfile.mkdtemp(prefix="dangerzone-")
|
||||||
result = self.run_cli(
|
result = self.run_cli(
|
||||||
f"{self.sample_doc} --output-filename {temp_dir}/safe.pdf"
|
f"{self.sample_doc} --output-filename {temp_dir}/safe.pdf"
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
result.assert_success()
|
||||||
|
|
||||||
def test_output_filename_new_dir(self):
|
def test_output_filename_new_dir(self):
|
||||||
result = self.run_cli(
|
result = self.run_cli(
|
||||||
f"{self.sample_doc} --output-filename fake-directory/my-output.pdf"
|
f"{self.sample_doc} --output-filename fake-directory/my-output.pdf"
|
||||||
)
|
)
|
||||||
assert result.exit_code != 0
|
result.assert_failure()
|
||||||
|
|
||||||
def test_sample_not_found(self):
|
def test_sample_not_found(self):
|
||||||
result = self.run_cli("fake-directory/fake-file.pdf")
|
result = self.run_cli("fake-directory/fake-file.pdf")
|
||||||
assert result.exit_code != 0
|
result.assert_failure()
|
||||||
|
|
||||||
def test_lang_eng(self):
|
def test_lang_eng(self):
|
||||||
result = self.run_cli(f'"{self.sample_doc}" --ocr-lang eng')
|
result = self.run_cli(f'"{self.sample_doc}" --ocr-lang eng')
|
||||||
assert result.exit_code == 0
|
result.assert_success()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"filename,",
|
"filename,",
|
||||||
|
@ -96,4 +192,4 @@ class TestCliConversion(TestCliBasic):
|
||||||
shutil.copyfile(self.sample_doc, doc_path)
|
shutil.copyfile(self.sample_doc, doc_path)
|
||||||
result = self.run_cli(doc_path)
|
result = self.run_cli(doc_path)
|
||||||
shutil.rmtree(tempdir)
|
shutil.rmtree(tempdir)
|
||||||
assert result.exit_code == 0
|
result.assert_success()
|
||||||
|
|
Loading…
Reference in a new issue