diff --git a/pyproject.toml b/pyproject.toml index 5dc6d9b..06c957d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ maintainers = [ {name = "Andreas Stefl", email="stefl.andreas@gmail.com"}, ] -requires-python = ">=3.7" +requires-python = ">=3.10" readme = "README.md" license = {file = "LICENSE.txt"} dependencies = [ diff --git a/src/htmlcmp/common.py b/src/htmlcmp/common.py index 4ab4154..9caa5f2 100644 --- a/src/htmlcmp/common.py +++ b/src/htmlcmp/common.py @@ -47,9 +47,9 @@ def compare_html(a: Path, b: Path, browser=None, diff_output: Path = None) -> bo raise FileNotFoundError("Both arguments must be files") if browser is None: - browser = get_browser() + browser = get_browser("firefox") diff, (image_a, image_b) = html_render_diff(a, b, browser=browser) - result = True if diff.getbbox() is None else False + result = diff.getbbox() is None if diff_output is not None and not result: diff_output.mkdir(parents=True, exist_ok=True) image_a.save(diff_output / "a.png") @@ -64,12 +64,19 @@ def compare_files(a: Path, b: Path, **kwargs) -> bool: if not a.is_file() or not b.is_file(): raise FileNotFoundError("Both arguments must be files") - if filecmp.cmp(a, b): + if filecmp.cmp(a, b, shallow=False): return True if a.suffix == ".json": return compare_json(a, b) if a.suffix == ".html": + if kwargs.get("browser") is None: + # No browser available (e.g. --driver none): the files already + # differ at the byte level and we cannot render to check for visual + # equality, so report them as different rather than spinning up a + # browser here. + return False return compare_html(a, b, **kwargs) + return False def comparable_file(path: Path) -> bool: diff --git a/src/htmlcmp/compare_output_server.py b/src/htmlcmp/compare_output_server.py index 8d10966..e641807 100755 --- a/src/htmlcmp/compare_output_server.py +++ b/src/htmlcmp/compare_output_server.py @@ -32,6 +32,10 @@ class Config: observer = None comparator = None browser = None + # Serializes access to the single shared ``browser`` above: Flask serves + # requests from a thread pool and Selenium drivers are not thread-safe, so + # concurrent /image_diff requests would otherwise interleave on one driver. + browser_lock = threading.Lock() thread_local = threading.local() log_file: Path = None @@ -722,11 +726,12 @@ def image_diff(path: str): if not (Config.path_a / path).is_file() or not (Config.path_b / path).is_file(): return "Image diff not available: file missing on one side", 404 - diff, _ = html_render_diff( - Config.path_a / path, - Config.path_b / path, - Config.browser, - ) + with Config.browser_lock: + diff, _ = html_render_diff( + Config.path_a / path, + Config.path_b / path, + Config.browser, + ) tmp = io.BytesIO() diff.save(tmp, "JPEG", quality=70) tmp.seek(0) @@ -786,6 +791,11 @@ def main(): parser.add_argument("--driver", choices=["chrome", "firefox"]) parser.add_argument("--max-workers", type=int, default=1) parser.add_argument("--compare", action="store_true") + parser.add_argument( + "--host", + default="0.0.0.0", + help="Host/interface to bind the server to (default: 0.0.0.0)", + ) parser.add_argument("--port", type=int, default=5000) parser.add_argument( "-v", @@ -820,7 +830,7 @@ def main(): Config.observer = Observer() Config.observer.start() - app.run(host="0.0.0.0", port=args.port) + app.run(host=args.host, port=args.port) if args.compare: Config.observer.stop() diff --git a/src/htmlcmp/html_render_diff.py b/src/htmlcmp/html_render_diff.py index dea29e6..cae4f34 100755 --- a/src/htmlcmp/html_render_diff.py +++ b/src/htmlcmp/html_render_diff.py @@ -108,8 +108,8 @@ def main(): parser.add_argument("a", type=Path, help="Path to the first HTML file") parser.add_argument("b", type=Path, help="Path to the second HTML file") parser.add_argument("--driver", choices=["chrome", "firefox"], default="firefox") - parser.add_argument("--max-width", default=1000) - parser.add_argument("--max-height", default=10000) + parser.add_argument("--max-width", type=int, default=1000) + parser.add_argument("--max-height", type=int, default=10000) args = parser.parse_args() browser = get_browser(args.driver, args.max_width, args.max_height) diff --git a/src/htmlcmp/tidy_output.py b/src/htmlcmp/tidy_output.py index 2dc64ed..bfd9624 100755 --- a/src/htmlcmp/tidy_output.py +++ b/src/htmlcmp/tidy_output.py @@ -112,12 +112,12 @@ def tidy_dir( "error": [], } - items = [p for p in path.iterdir()] - files = sorted([path for path in items if path.is_file() and tidyable_file(path)]) - dirs = sorted([path for path in items if path.is_dir()]) + items = list(path.iterdir()) + files = sorted(p for p in items if p.is_file() and tidyable_file(p)) + dirs = sorted(p for p in items if p.is_dir()) - for filename in [path.name for path in files]: - filepath = path / filename + for filepath in files: + filename = filepath.name tidy = tidy_file(filepath, html_tidy_config=html_tidy_config, verbose=verbose) if tidy == 0: print(f"{prefix_file}{bcolors.OKGREEN}{filename} ✓{bcolors.ENDC}") @@ -128,10 +128,10 @@ def tidy_dir( print(f"{prefix_file}{bcolors.FAIL}{filename} ✘{bcolors.ENDC}") result["error"].append(filepath) - for dirname in [path.name for path in dirs]: - print(prefix + "├── " + dirname) + for dirpath in dirs: + print(prefix + "├── " + dirpath.name) subresult = tidy_dir( - path / dirname, + dirpath, level=level + 1, prefix=prefix + "│ ", html_tidy_config=html_tidy_config,