diff --git a/.gitignore b/.gitignore index 4b7518c..c425004 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # HTML2PDF4Doc JS file. # html2pdf4doc/html2pdf4doc_js/ +.venv/ +.vscode .idea/ **/.wdm/ build/ diff --git a/html2pdf4doc/main.py b/html2pdf4doc/main.py index 0ef4c9c..3da31aa 100644 --- a/html2pdf4doc/main.py +++ b/html2pdf4doc/main.py @@ -295,7 +295,12 @@ def get_inches_from_millimeters(mm: float) -> float: return mm / 25.4 -def get_pdf_from_html(driver: webdriver.Chrome, url: str) -> Tuple[bytes, int]: +def get_pdf_from_html( + *, + driver: webdriver.Chrome, + url: str, + strict_mode_2: bool = False, +) -> Tuple[bytes, int]: print(f"html2pdf4doc: Opening URL with ChromeDriver: {url}", flush=True) # noqa: T201 driver.get(url) @@ -357,12 +362,29 @@ def __init__(self, page_count: int): ) sys.exit(1) + bad_logs: List[Dict[str, str]] = [] + print("html2pdf4doc: JS logs from the print session:") # noqa: T201 print('"""') # noqa: T201 for entry in logs: print(entry) # noqa: T201 + + if entry["level"] not in ("INFO", "DEBUG"): + bad_logs.append(entry) + print('"""') # noqa: T201 + if len(bad_logs) > 0: + bad_logs_error_message = ( + "html2pdf4doc: Something went wrong: " + "Detected console error/warning messages:\n" + f"{bad_logs}" + ) + if strict_mode_2: + raise RuntimeError(bad_logs_error_message) + else: + print(bad_logs_error_message) # noqa: T201 + # # Execute Print command with ChromeDriver. # @@ -530,11 +552,21 @@ def main() -> None: action="store_true", help=( "Enables Strict mode. In this mode, the library always performs a " - "validation of printed pages and raise a runtime error if the " + "validation of printed pages and raises a runtime error if the " "validation fails. Without the Strict mode enabled, only a warning " "message is printed and the execution continues." ), ) + command_parser_print.add_argument( + "--strict2", + action="store_true", + help=( + "Enables Strict mode (level 2). In this mode, the library always performs a " + "validation of console logs and raises a runtime error if there are " + "error/warning/severe messages. Without the Strict (level 2) mode enabled, " + "only a warning message is printed and the execution continues." + ), + ) command_parser_print.add_argument( "paths", nargs="+", help="Paths to input HTML file." ) @@ -587,7 +619,11 @@ def exit_handler() -> None: url = Path(os.path.abspath(path_to_input_html)).as_uri() - pdf_bytes, page_count = get_pdf_from_html(driver, url) + pdf_bytes, page_count = get_pdf_from_html( + driver=driver, + url=url, + strict_mode_2=args.strict2, + ) with open(path_to_output_pdf, "wb") as f: f.write(pdf_bytes) diff --git a/html2pdf4doc/main_fuzzer.py b/html2pdf4doc/main_fuzzer.py index deef5be..5dfa9dd 100644 --- a/html2pdf4doc/main_fuzzer.py +++ b/html2pdf4doc/main_fuzzer.py @@ -26,7 +26,14 @@ def measure_performance(title: str) -> Iterator[None]: print(f"{padded_name}{padded_time}s", flush=True) # noqa: T201 -def mutate_and_print(path_to_input_file: str, path_to_root: str) -> bool: +def mutate_and_print( + *, + path_to_input_file: str, + path_to_root: str, + path_to_failed_mutants_dir: str, + strict_mode: bool = False, + strict_mode_2: bool = False, +) -> bool: assert os.path.isfile(path_to_input_file), path_to_input_file assert os.path.isdir(path_to_root), path_to_root if not os.path.abspath(path_to_root): @@ -72,25 +79,31 @@ def mutate_and_print(path_to_input_file: str, path_to_root: str) -> bool: "-m", "html2pdf4doc.main", "print", - "--strict", ] + if strict_mode: + cmd.append("--strict") + if strict_mode_2: + cmd.append("--strict2") for path_to_print_ in paths_to_print: cmd.append(path_to_print_[0]) cmd.append(path_to_print_[1]) relative_path_to_mut_html = Path(path_to_mut_html).relative_to(path_to_root) - path_to_mut_output = f"output/{relative_path_to_mut_html}" + path_to_mut_output = os.path.join( + path_to_failed_mutants_dir, relative_path_to_mut_html + ) def copy_files_if_needed() -> None: if os.path.isdir(path_to_mut_output): return - shutil.rmtree("output", ignore_errors=True) - Path("output").mkdir(parents=True, exist_ok=True) + Path(path_to_failed_mutants_dir).mkdir(parents=True, exist_ok=True) shutil.copytree( - "html2pdf4doc", "output/html2pdf4doc", dirs_exist_ok=True + "html2pdf4doc", + os.path.join(path_to_failed_mutants_dir, "html2pdf4doc"), + dirs_exist_ok=True, ) shutil.rmtree(path_to_mut_output, ignore_errors=True) @@ -110,6 +123,12 @@ def copy_mutated_file() -> None: ) shutil.copy(path_to_mut_html, path_to_mut_html_out) + if not os.path.isfile(path_to_mut_pdf): + print( # noqa: T201 + f"html2pdf4doc_fuzzer: warning: Mutated PDF is missing: {path_to_mut_pdf}" + ) + return + path_to_mut_pdf_out = os.path.join( path_to_mut_output, f"{relative_path_to_mut_html}.{timestamp}.pdf", @@ -143,11 +162,14 @@ def copy_mutated_file() -> None: def fuzz_test( - *, path_to_input_file: str, path_to_root: str, total_mutations: int = 20 + *, + path_to_input_file: str, + path_to_root: str, + path_to_failed_mutants_dir: str, + total_mutations: int = 20, + strict_mode: bool = False, + strict_mode_2: bool = False, ) -> None: - shutil.rmtree("output", ignore_errors=True) - Path("output").mkdir(parents=True, exist_ok=True) - success_count, failure_count = 0, 0 for i in range(1, total_mutations + 1): print( # noqa: T201 @@ -155,7 +177,13 @@ def fuzz_test( f"So far: 🟢{success_count} / 🔴{failure_count}", flush=True, ) - success = mutate_and_print(path_to_input_file, path_to_root) + success = mutate_and_print( + path_to_input_file=path_to_input_file, + path_to_root=path_to_root, + path_to_failed_mutants_dir=path_to_failed_mutants_dir, + strict_mode=strict_mode, + strict_mode_2=strict_mode_2, + ) if success: success_count += 1 else: @@ -182,24 +210,41 @@ def main() -> None: parser.add_argument("input_file", type=str, help="TODO") parser.add_argument("root_path", type=str, help="TODO") + parser.add_argument("path_to_failed_mutants_dir", type=str, help="TODO") parser.add_argument( "--total-mutations", type=int, - choices=range(1, 1001), required=True, help="An integer between 1 and 1000", ) - + parser.add_argument( + "--strict", + action="store_true", + help="Enables Strict mode (level 1).", + ) + parser.add_argument( + "--strict2", + action="store_true", + help="Enables Strict mode (level 2).", + ) args = parser.parse_args() path_to_input_file = args.input_file path_to_root = args.root_path + path_to_failed_mutants_dir = args.path_to_failed_mutants_dir total_mutations = args.total_mutations + assert 1 <= total_mutations <= 1000, total_mutations + + strict_mode = args.strict + strict_mode_2 = args.strict2 fuzz_test( path_to_input_file=path_to_input_file, path_to_root=path_to_root, + path_to_failed_mutants_dir=path_to_failed_mutants_dir, total_mutations=total_mutations, + strict_mode=strict_mode, + strict_mode_2=strict_mode_2, ) diff --git a/tasks.py b/tasks.py index a98d938..22fdc3d 100644 --- a/tasks.py +++ b/tasks.py @@ -210,7 +210,7 @@ def test_fuzz(context, focus=None, total_mutations: int = 10, output=False): focus_argument = f"-k {focus}" if focus is not None else "" long_argument = ( - f"--fuzz-total-mutations {total_mutations}" if total_mutations else "" + f"--fuzz-total-mutations={total_mutations}" if total_mutations else "" ) output_argument = "--capture=no" if output else "" diff --git a/tests/fuzz/01_strictdoc_guide_202510/test_case.py b/tests/fuzz/01_strictdoc_guide_202510/test_case.py index 37adc47..25a57f8 100644 --- a/tests/fuzz/01_strictdoc_guide_202510/test_case.py +++ b/tests/fuzz/01_strictdoc_guide_202510/test_case.py @@ -1,7 +1,7 @@ import os from html2pdf4doc.main_fuzzer import fuzz_test -from tests.fuzz.conftest import create_build_folder, FuzzConfig +from tests.fuzz.conftest import create_build_folder, FuzzConfig, create_failed_mutants_folder PATH_TO_THIS_FOLDER = os.path.dirname(__file__) @@ -14,5 +14,8 @@ def test(fuzz_config: FuzzConfig): "strictdoc/docs/strictdoc_01_user_guide-PDF.html" ), path_to_root=build_folder, - total_mutations=fuzz_config.total_mutations + path_to_failed_mutants_dir=create_failed_mutants_folder(PATH_TO_THIS_FOLDER), + total_mutations=fuzz_config.total_mutations, + strict_mode=True, + strict_mode_2=False, ) diff --git a/tests/fuzz/conftest.py b/tests/fuzz/conftest.py index 9bf69ab..90fe516 100644 --- a/tests/fuzz/conftest.py +++ b/tests/fuzz/conftest.py @@ -1,3 +1,4 @@ +import datetime import os import shutil from dataclasses import dataclass @@ -46,3 +47,19 @@ def create_build_folder(test_folder: str) -> str: shutil.copytree(test_folder, build_folder) return build_folder + + +def create_failed_mutants_folder(test_folder: str) -> str: + assert os.path.isdir(test_folder), test_folder + assert os.path.isabs(test_folder), test_folder + + relative_path_to_test_folder = Path(test_folder).relative_to(PATH_TO_TESTS_FUZZ_FOLDER) + + mutants_folder = os.path.join( + "build", + "tests_fuzz_failed_mutants", + datetime.datetime.now().strftime("%Y%m%d_%H%M%S"), + relative_path_to_test_folder + ) + + return mutants_folder diff --git a/tests/integration/20_fuzz_integration/test.itest b/tests/integration/20_fuzz_integration/test.itest index a6ff63e..d261940 100644 --- a/tests/integration/20_fuzz_integration/test.itest +++ b/tests/integration/20_fuzz_integration/test.itest @@ -4,5 +4,5 @@ RUN: mkdir -p %project_root/build/tests_integration_fuzz/ RUN: cp -rv %project_root/tests/fuzz/01_strictdoc_guide_202510 %project_root/build/tests_integration_fuzz/ -RUN: PYTHONPATH=%project_root python -m html2pdf4doc.main_fuzzer %project_root/build/tests_integration_fuzz/01_strictdoc_guide_202510/strictdoc/docs/strictdoc_01_user_guide-PDF.html %project_root/build/tests_integration_fuzz/01_strictdoc_guide_202510 --total-mutations 1 | filecheck %s --dump-input=fail +RUN: PYTHONPATH=%project_root python -m html2pdf4doc.main_fuzzer %project_root/build/tests_integration_fuzz/01_strictdoc_guide_202510/strictdoc/docs/strictdoc_01_user_guide-PDF.html %project_root/build/tests_integration_fuzz/01_strictdoc_guide_202510 %T/ --total-mutations 1 | filecheck %s --dump-input=fail CHECK: html2pdf4doc_fuzzer: finished ✅ — Success rate: 1/1 (100.0%)