Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci-mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

jobs:
build:
runs-on: macos-13
runs-on: macos-latest

strategy:
matrix:
Expand All @@ -15,10 +15,10 @@ jobs:
]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ __pycache__/

# Fuzz testing files.
**.mut.**

.coverage
50 changes: 39 additions & 11 deletions html2pdf4doc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
import zipfile
from datetime import datetime
from enum import IntEnum
from pathlib import Path
from time import sleep, time
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
Expand Down Expand Up @@ -58,6 +59,18 @@ def extract_page_count(logs: List[Dict[str, str]]) -> int:
raise ValueError("No page count found in logs.")


class HPDExitCode(IntEnum):
GENERAL_ERROR = 1
COULD_NOT_FIND_CHROME = 5
DID_NOT_RECEIVE_SUCCESS_STATUS_FROM_HTML2PDF4DOC_JS = 6


class HPDError(RuntimeError):
def __init__(self, message: str, exit_code: int):
super().__init__(message)
self.exit_code: int = exit_code


class IntRange:
def __init__(self, imin: int, imax: int) -> None:
self.imin: int = imin
Expand Down Expand Up @@ -87,9 +100,9 @@ def get_chrome_driver(self, path_to_cache_dir: str) -> str:

# If Web Driver Manager cannot detect Chrome, it returns None.
if chrome_version is None:
raise RuntimeError(
"html2pdf4doc: "
"Web Driver Manager could not detect an existing Chrome installation."
raise HPDError(
"Web Driver Manager could not detect an existing Chrome installation.",
exit_code=HPDExitCode.COULD_NOT_FIND_CHROME,
)

chrome_major_version = chrome_version.split(".")[0]
Expand Down Expand Up @@ -145,15 +158,16 @@ def get_chrome_driver(self, path_to_cache_dir: str) -> str:

return path_to_downloaded_chrome_driver

@staticmethod
@classmethod
def _download_chromedriver(
cls,
chrome_major_version: str,
os_type: str,
path_to_driver_cache_dir: str,
path_to_cached_chrome_driver: str,
) -> str:
url = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
response = ChromeDriverManager.send_http_get_request(url)
response = cls.send_http_get_request(url)
if response is None:
raise RuntimeError(
"Could not download known-good-versions-with-downloads.json"
Expand Down Expand Up @@ -195,7 +209,7 @@ def _download_chromedriver(
print( # noqa: T201
f"html2pdf4doc: downloading ChromeDriver from: {driver_url}"
)
response = ChromeDriverManager.send_http_get_request(driver_url)
response = cls.send_http_get_request(driver_url)

if response is None:
raise RuntimeError(
Expand Down Expand Up @@ -360,7 +374,9 @@ def __init__(self, page_count: int):
"error: html2pdf4doc: "
"could not receive a successful completion status from HTML2PDF4Doc."
)
sys.exit(1)
sys.exit(
HPDExitCode.DID_NOT_RECEIVE_SUCCESS_STATUS_FROM_HTML2PDF4DOC_JS
)

bad_logs: List[Dict[str, str]] = []

Expand Down Expand Up @@ -402,6 +418,7 @@ def __init__(self, page_count: int):


def create_webdriver(
chrome_driver_manager: ChromeDriverManager,
chromedriver_argument: Optional[str],
path_to_cache_dir: str,
page_load_timeout: int,
Expand All @@ -411,7 +428,7 @@ def create_webdriver(

path_to_chrome_driver: str
if chromedriver_argument is None:
path_to_chrome_driver = ChromeDriverManager().get_chrome_driver(
path_to_chrome_driver = chrome_driver_manager.get_chrome_driver(
path_to_cache_dir
)
else:
Expand Down Expand Up @@ -475,7 +492,7 @@ def create_webdriver(
return driver


def main() -> None:
def _main() -> None:
if not os.path.isfile(PATH_TO_HTML2PDF4DOC_JS):
raise RuntimeError(
f"Corrupted html2pdf4doc package bundle. "
Expand Down Expand Up @@ -573,13 +590,15 @@ def main() -> None:

args = parser.parse_args()

chrome_driver_manager = ChromeDriverManager()

path_to_cache_dir: str
if args.command == "get_driver":
path_to_cache_dir = (
args.cache_dir if args.cache_dir is not None else DEFAULT_CACHE_DIR
)

path_to_chrome = ChromeDriverManager().get_chrome_driver(
path_to_chrome = chrome_driver_manager.get_chrome_driver(
path_to_cache_dir
)
print(f"html2pdf4doc: ChromeDriver available at path: {path_to_chrome}") # noqa: T201
Expand All @@ -594,6 +613,7 @@ def main() -> None:
args.cache_dir if args.cache_dir is not None else DEFAULT_CACHE_DIR
)
driver: webdriver.Chrome = create_webdriver(
chrome_driver_manager,
args.chromedriver,
path_to_cache_dir,
page_load_timeout,
Expand Down Expand Up @@ -645,7 +665,15 @@ def exit_handler() -> None:

else:
print("html2pdf4doc: unknown command.") # noqa: T201
sys.exit(1)
sys.exit(HPDExitCode.GENERAL_ERROR)


def main() -> None:
try:
_main()
except HPDError as error_:
print("error: html2pdf4doc: " + str(error_), flush=True) # noqa: T201
sys.exit(error_.exit_code)


if __name__ == "__main__":
Expand Down
6 changes: 6 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[mypy]

# We ignore build folder but mypy still follows into the Python packages.
# The following is a way to skip it.
[mypy-_pytest.*]
follow_imports = skip
1 change: 1 addition & 0 deletions requirements.development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ruff>=0.9
# Unit tests
#
pytest
coverage

#
# Integration tests
Expand Down
28 changes: 26 additions & 2 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def lint_ruff_format(context):
format
*.py
html2pdf4doc/
tests/unit/
tests/integration/
""",
)
Expand All @@ -131,7 +132,7 @@ def lint_ruff(context):
run_invoke(
context,
"""
ruff check *.py html2pdf4doc/ --fix --cache-dir build/ruff
ruff check *.py html2pdf4doc/ tests/unit/ --fix --cache-dir build/ruff
""",
)

Expand All @@ -144,7 +145,7 @@ def lint_mypy(context):
run_invoke(
context,
"""
mypy html2pdf4doc/
mypy html2pdf4doc/ tests/unit/
--show-error-codes
--disable-error-code=import
--disable-error-code=misc
Expand All @@ -162,6 +163,29 @@ def lint(context):
lint_mypy(context)


@task(aliases=["tu"])
def test_unit(context, focus=None, output=False):
focus_argument = f"-k {focus}" if focus is not None else ""
output_argument = "--capture=no" if output else ""

cwd = os.getcwd()

path_to_coverage_file = f"{cwd}/build/coverage/unit/.coverage"
run_invoke(
context,
f"""
coverage run
--data-file={path_to_coverage_file}
-m pytest
{focus_argument}
{output_argument}
-o cache_dir=build/pytest_unit
-o junit_suite_name="HTML2PDF4Doc unit tests"
tests/unit/
""",
)


@task(aliases=["ti"])
def test_integration(
context,
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/test_chrome_driver_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import tempfile
from typing import Optional

import pytest

from html2pdf4doc.main import ChromeDriverManager, HPDError, HPDExitCode


class FailingChromeDriverManager(ChromeDriverManager):
@staticmethod
def get_chrome_version() -> Optional[str]:
return None


def test_raises_error_when_cannot_detect_chrome() -> None:
"""
This first unit test is not great but it is a good start anyway.
"""

chrome_driver_manager = FailingChromeDriverManager()

with tempfile.TemporaryDirectory() as tmpdir:
with pytest.raises(Exception) as exc_info:
_ = chrome_driver_manager.get_chrome_driver(tmpdir)

assert exc_info.type is HPDError
assert exc_info.value.exit_code == HPDExitCode.COULD_NOT_FIND_CHROME